Котлин «не ожидал никаких параметров» при попытке вернуть встроенную лямбду

Я пытаюсь написать функцию Kotlin, которая возвращает лямбда, принимая параметр. Я пытаюсь использовать для этого следующий код:

fun <T> makeFunc() : (T.() -> Unit) { return { t: T -> print("Foo") } } 

Примечание. В реальной программе функция более сложна и использует t .

Котлин отвергает это как недействительное, давая ошибку «Ожидаемые без параметров» при t: T

Однако присвоение этой лямбда переменной сначала не отклоняется и отлично работает:

 fun <T> makeFunc() : (T.() -> Unit) { val x = { t: T -> print("Foo") } return x } 

Эти два фрагмента кажутся одинаковыми, так почему это так? Являются ли фигурные скобки после утверждения return интерпретированными как нечто иное, чем лямбда?

Кроме того, IntelliJ сообщает мне, что значение переменной может быть встроено, тогда как это вызывает ошибку.

введите описание изображения здесь

Любопытный момент в дизайне функциональных типов и лямбда-выражений в Котлине.

Фактически, поведение может быть описано в этих двух утверждениях:

  • Именованные значения функциональных типов взаимозаменяемы между обычным функциональным типом типа (A, B) -> C и соответствующим типом функции с первым параметром, преобразованным в приемник A.(B) -> C Эти типы назначаются друг от друга .

    Поэтому, когда вы объявляете переменную, которая вводится как (T) -> Unit , вы можете передать ее или использовать ее там, где ожидается T.() -> Unit , и наоборот.

  • Лямбда-выражения, однако, не могут использоваться таким свободным образом.

    Когда ожидается функция с приемником T.() -> Unit , вы не можете поместить лямбда с параметром T в эту позицию, лямбда должна точно соответствовать сигнатуре, приемник и первый параметр не могут быть преобразованы друг в друга:

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

    (из документа, указанного выше )

    Это правило упрощает чтение lambdas: они всегда соответствуют ожидаемому типу. Например, нет никакой двусмысленности между лямбдой с приемником и лямбдой с неявным, которое просто не используется.

Для сравнения:

 fun foo(bar: (A) -> B) = Unit fun baz(qux: A.() -> B) = Unit val f: (A) -> B = { TODO() } val g: A.() -> B = { TODO() } foo(f) // OK foo(g) // OK baz(f) // OK baz(g) // OK // But: foo { a: A -> println(a); TODO() } // OK foo { println(this@foo); TODO() } // Error baz { println(this@baz); TODO() } // OK baz { a: A -> println(a); TODO() } // Error 

В принципе, здесь диагностика IDE неверна. Сообщите об этом в качестве ошибки для трекера Kotlin.

Вы определяете тип функции () -> Unit на приемнике T , для этой функции действительно нет параметра, см. "()" . Ошибка имеет смысл. Поскольку вы определяете тип функции с T качестве приемника, вы можете обратиться к T this :

 fun <T> makeFunc(): (T.() -> Unit) { return { print(this) } }