TLDR: Лямбда-выражения не могут иметь параметры по умолчанию. Если они вам нужны, вы должны объявить функцию (может быть локально внутри другой функции).
Чтобы уточнить, давайте рассмотрим различные способы определения функционально-подобных типов в Kotlin. Интуитивно понятно, что они будут работать одинаково, но в их функциональных возможностях есть тонкие несоответствия.
1. Перегруженные функции
При определении перегрузок функций вручную (способ Java) можно не только вызывать функцию с любым разрешенным номером аргумента, но также store ссылка на функцию в типе с использованием любого номера аргумента.
fun overload(min: Int, max: Int) = (min..max).random()
fun overload(min: Int) = overload(min, 12)
fun overload() = overload(1, 12)
// Calling is possible with all numbers of arguments, and naming ones at the end
overload()
overload(3)
overload(min=3)
overload(3, 4)
overload(3, max=4)
overload(min=3, max=4)
// Intuitively, all 3 ways of storing work:
val f: (Int, Int) -> Int = ::overload
val g: (Int) -> Int = ::overload
val h: () -> Int = ::overload
// On the other hand, this does NOT compile because of ambiguity:
val i = ::overload
2. Функции с параметрами по умолчанию
Более идиоматическим в Kotlin является использование параметров по умолчанию. Хотя это, по-видимому, в основном эквивалентно перегруженным функциям, это не так. Заметное отличие состоит в том, что объявляется только одна функция, и вывод типа будет учитывать различное количество аргументов только при вызове функции, но не при сохранении ее через ссылку на функцию.
fun default(min: Int = 1, max: Int = 12) = (min..max).random()
// Calling is possible exactly like overloaded functions
default()
default(3)
default(min=3)
default(3, 4)
default(3, max=4)
default(min=3, max=4)
// No ambiguity, f and g have the same type (all parameters)
val f = ::default
val g: (Int, Int) -> Int = ::default
// However, storing in a function type taking fewer arguments is NOT possible
val h: (Int) -> Int = ::default
val i: () -> Int = ::default
3. Анонимные функции
Анонимные функции не допускают параметров по умолчанию даже в объявлении, поэтому есть только один способ их вызова. Кроме того, переменная, хранящая их, имеет тип функции, которая теряет информацию об именах параметров и, таким образом, предотвращает вызов с именованными аргументами.
val anonymous = fun(min: Int, max: Int) = (min..max).random()
val anonymous: (Int, Int) -> Int = fun(min: Int, max: Int) = (min..max).random()
// Only one way to call
anonymous(3, 4)
// No ambiguity, f and g have the same (full type)
val f = anonymous
val g: (Int, Int) -> Int = anonymous
// Mistake, which compiles: this declares h as a *property*,
// with type KProperty<(Int, Int) -> Int>
val h = ::anonymous
// Calling with named arguments is NOT possible
anonymous(3, 4) // OK
anonymous(min=3, max=4) // error
4. Лямбда-выражения
Как и анонимные функции, лямбда-выражения не допускают параметров по умолчанию и не могут быть вызваны с именованными аргументами. Поскольку они сразу сохраняются как тип функции, такой как (Int, Int) -> Int
, они подвергаются тем же ограничениям, что и типы функций, относящиеся к реальным функциям.
Вывод типа работает только в том случае, если типы параметров указаны либо в лямбда-выражении, либов типе функции, который нужно назначить:
// OK:
val lambda = { min: Int, max: Int -> (min..max).random() }
val lambda2: (Int, Int) -> Int = { min, max -> (min..max).random() }
// Type inference fails:
val lambda3 = { min, max -> (min..max).random() }
Основной вывод здесь заключается в том, что эти 4 вызова, хотя и поддерживают одинаковые базовые функции, отличаются в следующих точках:
- Позволяет объявление и вызов параметров по умолчанию
- Позволяет сохранение через ссылку на функцию, которая учитывает параметры по умолчанию
- Позволяет вызывать с именованными аргументами
Обращаясь к вызываемым объектам как к типам функций (что является единственной опцией для анонимных функций и лямбда-выражений), вы теряете информацию, присутствующую в исходном объявлении.