10

I have a simple factory pattern where the implementation is determined through overload resolution. Problem is that the Kotlin compiler complains with "Overload resolution ambiguity.." for the inline lambda.

class Foo(){
    companion object Factory {
        fun create(x: Int, f: (Int) -> Double) = 2.0
        fun create(x: Int, f: (Int) -> Int) = 1
    }
}

fun main(args:Array<String>){
    val a =  Foo.create(1,::fromDouble) //OK
    val b =  Foo.create(1,::fromInt)  //OK
    val ambiguous =  Foo.create(1){i -> 1.0}  //Overload resolution ambiguity?
}


fun fromDouble(int:Int)  = 1.0
fun fromInt(int:Int)  = 1

How does the Kotlin compiler resolve overload resolution and why is the inline lambda considered to be ambiguous?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Tomas Karlsson
  • 705
  • 1
  • 6
  • 17
  • 5
    This seems to be a bug, because if I cast the lambda like `{ i: Int -> 1.0 } as (Int) -> Double` there's no ambiguity, but it says that cast is not needed. Also, if I extract the lambda to `val l = { i: Int -> 1.0 }` and use it, again there's no ambiguity. Please search the bug tracker for this issue and if it's not there file a new one: https://youtrack.jetbrains.com/issues/KT – hotkey Mar 01 '16 at 20:37
  • 1
    Another interesting thing is if you cast the lambda as @hotkey does, the IDE will tell you that it's not necessary. But the moment you remove it, it complains about ambiguity. – Doug Stevenson Mar 01 '16 at 20:51
  • 2
    Thanks for the input! Figured that it probably was a bug. Filed a report at https://youtrack.jetbrains.com/issue/KT-11265 – Tomas Karlsson Mar 02 '16 at 08:30
  • This still seems to be a bug. – Johann Dec 05 '19 at 18:50

2 Answers2

5

Kotlin compiler resolves every expression only once. So when compiler starts resolution for lambda expression, it should know types of lambda arguments. Because of this compiler should choose one of methods create before it starts to look inside lambda.

Example:

fun foo(f: (Int) -> Int) = 1
fun foo(f: (String) -> String) = ""
val bar = foo { 
   println(it) 
   5
}

Here we can't choose one of functions foo because no one of them is more specific that another, so we can't start resolution for lambda expression because we don't know type for it.

In your example it is in theory possible to start resolution for lambda before choosing particular function because for all potential functions types of lambda arguments are the same. But it is untrivial logic which might be hard to implement.

erokhins
  • 619
  • 5
  • 7
  • 2
    That's a bummer. Overload resolution on lambda is a very usefull for numerical application. Let say I have a matrix class that work on float, double and int. With "overload resolution" instancing would be `val ms = Mat44{i,j -> 1.0}` , now I'm forced to write `val ms = Mat44({i:Int,j:Int -> 1.0} as (Int,Int) -> Double )` which is just plain and silly verbose for such a trivial task. Many thanks @erokhins for a great explanation! – Tomas Karlsson Mar 07 '16 at 09:14
  • As workaround you can create functions with different names: `fun Mat44Int2Double(f: (Int, Int) -> Double) = Mat44(f)`. But it isn't elegant... – erokhins Mar 07 '16 at 14:01
  • This same issue also applies for lambdas in constructors. You'll get an overload ambiguity as well. – Johann Dec 05 '19 at 18:52
0

For anyone else running into this in 2023, there is a workaround to make Kotlin look at lambda return types when resolving overloads (which it normally doesn't do).

As shown here, you must opt in to experimental type inference and annotate the methods accordingly:

import kotlin.experimental.ExperimentalTypeInference

@OptIn(ExperimentalTypeInference::class) // Can be done per method, class or file
@OverloadResolutionByLambdaReturnType
fun bar(f: () -> Int): String {
    return "${f()}"
}

fun bar(f: () -> String): String {
    return f()
}

fun main() {
    println(
        bar {
            3
        }
    )
}
Modus Operandi
  • 552
  • 1
  • 6
  • 24