Лямбда-выражения

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

Лямбда-выражения иногда называют анонимными функциями, потому что у них нет имен. Однако в Kotlin анонимные функции есть сами по себе, то есть они и лямбды – несколько разные вещи. Анонимные функции в Kotlin определяются через ключевое слово fun как обычные функции, хотя не имеют имени. В то же время лямбда-выражения определяются заключением их в фигурные скобки.

Рассмотрим простейшее лямбда-выражение:

fun main() {
    {println(1)}
}

Выполнение этого кода ни к чему не приведет, хотя мы могли бы ожидать появления на экране единицы. Заключив выражение println(1) в фигурные скобки, мы превратили его в тело функции. Пусть эта функция и лямбда, она все-таки функция, и без вызова ее тело не выполняется. В случае обычной функции, чтобы ее вызвать, надо обратиться к ней по имени и после имени поставить круглые скобки, даже если мы не передаем в функцию никаких аргументов. Аналогично делается и в случае лямбда-функции. Из-за отсутствия имени вызывать ее будем сразу после определения:

fun main() {
    {println(1)}()
}

Данный код выведет на экран единицу.

Усовершенствуем функцию так, чтобы она выводила на экран переданный ей аргумент. Лямбда-выражения, как и обычные функции, могут иметь параметры. Они указываются в начале и отделяются от тела "стрелкой" – знаком минуса и угловой скобкой, то есть "->".

fun main() {
    {n: Int -> println(n)}(10)
}

Лямбда-функции не только принимают аргументы, но и возвращают значения. Лямбда возвращает результат выполнения последнего выражения ее тела. В нашем случае тело состоит из одной строки. Функция println() возвращает в место своего вызова объект Unit. Этот объект лямбда передаст в место своего вызова. Мы можем присвоить объект переменной и увидеть его.

fun main() {
    val a = {n: Int -> println(n)}(10) // выведет: 10
    println(a) // выведет: kotlin.Unit
}

Тело лямбда-функции может быть сложнее, а параметров быть больше.

fun main() {
    val a = {n: Int, m: Int ->
        println(n + m)
        n + m
    }(10, 20) 
 
    println(a)
}

Код выше два раза выведет 30. Первое число будет результатом выполнения выражения println(n + m) в теле лямбды. Второе – будет присвоено переменной a, потому что выражение n + m последнее в теле лямбды и его результат присваивается переменной a, значение которой потом выводится на экран.

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

fun main() {
    val a = {n: Int, m: Int -> n + m}
    val b = a(10, 20)
    val c = a(30, 40)
 
    println(a)
    println(b)
    println(c)
}

Функциональный тип переменной

Переменные b и c будут иметь целочисленный тип, потому что им присваивается результат выполнения функции, то есть то, что из нее было возвращено, а не сама функция. Переменной a присваивается функция. Такая переменная имеет функциональный тип – особую разновидность объектов. В данном случае тип функциональной переменной определен как (Int, Int) -> Int. Это значит, что она принимает два целочисленных аргумента и возвращает число.

При описании конкретного функционального типа сначала в скобках указывается тип параметров, после стрелки – тип возвращаемого значения. Как и любой другой тип, компилятор Котлина может вывести функциональный тип автоматически из определения функции. Однако мы можем указать его вручную. При этом записывать типы в самом определении лямбды необязательно.

val a: (Int, Int) -> Int = {n, m -> n + m}

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

Именованные параметры лямбда-выражения

Вернемся к варианту лямбда-функции с одним параметром:

fun main() {
    val a = {n: Any -> println(n)}
    a(10)
    a(30)
}

Если параметр только один, в Котлин он по-умолчанию присваивается встроенному идентификатору it. Используя его, можно не объявлять параметр. Уже само наличие в теле it будет говорить, что у лямбды один параметр. При этом, чтобы было понятно, какого типа этот параметр, функциональный тип переменной придется указать явно.

val a: (Any) -> Unit = {println(it)}

В других ситуациях Kotlin способен сам вывести тип, что называется, из контекста. Подобное происходит при вызове методов коллекций, принимающих в качестве одного из аргументов лямбда-выражения. Там без явных указаний it становится типом элементов коллекций.