Kotlin: встроенная лямбда и неоднозначность разрешения перегрузки

У меня есть простой фабричный шаблон, где реализация определяется разрешением перегрузки. Проблема в том, что компилятор Kotlin жалуется на «Неоднозначность разрешения перегрузки..» для встроенной лямбды.

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

Как компилятор Kotlin разрешает разрешение перегрузки и почему встроенная лямбда считается неоднозначной?


person Tomas Karlsson    schedule 01.03.2016    source источник
comment
Это похоже на ошибку, потому что если я приведу лямбда как { i: Int -> 1.0 } as (Int) -> Double, двусмысленности не будет, но он говорит, что приведение не требуется. Кроме того, если я извлеку лямбду в val l = { i: Int -> 1.0 } и использую ее, опять же, двусмысленности не будет. Пожалуйста, найдите эту проблему в системе отслеживания ошибок и, если ее нет, отправьте новую: youtrack.jetbrains.com/ выпусков/КТ   -  person hotkey    schedule 01.03.2016
comment
Еще одна интересная вещь: если вы примените лямбду, как это делает @hotkey, IDE скажет вам, что в этом нет необходимости. Но в тот момент, когда вы удаляете его, он жалуется на двусмысленность.   -  person Doug Stevenson    schedule 01.03.2016
comment
Спасибо за отзыв! Подумал, что скорее всего это баг. Отчет отправлен по адресу youtrack.jetbrains.com/issue/KT-11265.   -  person Tomas Karlsson    schedule 02.03.2016
comment
Это все еще кажется ошибкой.   -  person AndroidDev    schedule 05.12.2019


Ответы (1)


Компилятор Kotlin разрешает каждое выражение только один раз. Поэтому, когда компилятор начинает разрешение лямбда-выражения, он должен знать типы лямбда-аргументов. Из-за этого компилятор должен выбрать один из методов create прежде, чем он начнет смотреть внутрь лямбды.

Пример:

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

Здесь мы не можем выбрать одну из функций foo, потому что ни одна из них не является более конкретной, чем другая, поэтому мы не можем запустить разрешение для лямбда-выражения, потому что мы не знаем тип для it.

В вашем примере теоретически возможно начать разрешение для лямбда перед выбором конкретной функции, потому что для всех потенциальных функций типы лямбда-аргументов одинаковы. Но это нетривиальная логика, которую может быть трудно реализовать.

person erokhins    schedule 03.03.2016
comment
Это облом. Разрешение перегрузки по лямбде очень полезно для численного применения. Скажем, у меня есть матричный класс, который работает с числами с плавающей запятой, двойным числом и целым числом. С экземпляром разрешения перегрузки будет val ms = Mat44{i,j -> 1.0} , теперь я вынужден написать val ms = Mat44({i:Int,j:Int -> 1.0} as (Int,Int) -> Double ), что просто и глупо многословно для такой тривиальной задачи. Большое спасибо @erokhins за отличное объяснение! - person Tomas Karlsson; 07.03.2016
comment
В качестве обходного пути вы можете создавать функции с разными именами: fun Mat44Int2Double(f: (Int, Int) -> Double) = Mat44(f). Но не элегантно... - person erokhins; 07.03.2016
comment
Эта же проблема также относится к лямбда-выражениям в конструкторах. Вы также получите неоднозначность перегрузки. - person AndroidDev; 05.12.2019