Skip to content

Latest commit

 

History

History
289 lines (219 loc) · 20.2 KB

lambdas.md

File metadata and controls

289 lines (219 loc) · 20.2 KB
type layout title url
doc
reference
Лямбды

Высокоуровневые функции и лямбды

Функции высшего порядка

Функция высшего порядка - это функция, которая принимает функции как параметры, или возвращает функцию в качестве результата. Хорошим примером такой функции является lock(), которая берёт блокирующий объект и функцию, получает блокировку, выполняет функцию и отпускает блокировку:

fun <T> lock(lock: Lock, body: () -> T): T{
  lock.lock()
  try{
      return body()
  }
  finally {
      lock.unlock()
  }
}

Проанализируем этот код: body имеет функциональный тип: () -> T, то есть параметр должен быть функцией без параметров, возвращающей значение типа T. Она вызывается внутри блока try, под защитой объекта lock, получившего блокировку вызовом функции lock().

Если мы хотим вызвать метод lock(), можно передать другую функцию в качестве входящего аргумента (подробно читайте Ссылки на функции):

fun toBeSynchronized() = sharedResource.operation()

val result = lock (lock, ::toBeSynchronized)

Обычно удобней передавать лямбда-выражения:

val result = lock(lock, { sharedResource.operation() })

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

  • Лямбда-выражение всегда заключено в фигурные скобки;
  • Его параметры (если они есть) объявлены до знака -> (допустимо не указывать параметры);
  • Тело выражения следует после знака ->.

В Kotlin есть конвенция, согласно которой, если последний параметр функции является функцией, которая передается в виде лямбда-выражения, можно вынести его за скобки:

lock (lock) {
    sharedResource.operation()
}

Следующим примером функции высшего порядка выберем функцию map():

fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
    val result = arrayListOf<R>()
    for (item in this)
        result.add(transform(item))
    return result
}

Эту функцию можно вызвать так:

val doubled = ints.map { it -> it * 2 }

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

Ключевое слово it: неявное имя единственного параметра

Ещё одна полезная конвенция состоит в том, что если функциональный литерал имеет ровно один параметр, его объявление можно удалить (вместе с ->), и обращаться к нему по имени it:

ints.map { it * 2 }

Эти конвенции позволяют писать код в стиле LINQ:

strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() }

Символ подчеркивания для неиспользуемых переменных (since 1.1)

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

map.forEach { _, value -> println("$value!") }

Деструктуризация в лямбдах (since 1.1)

Деструктуризация в лямбдах описана в деструктурирующие объявления.

Инлайн функции

Иногда выгодно улучшить производительность функций высшего порядка, используя инлайн функции.

Лямбда-выражения и анонимные функции

Лямбда-выражение, или анонимная функция, это "функциональный литерал", то есть необъявленная функция, которая немедленно используется в качестве выражения. Рассмотрим следующий пример:

max(strings, { a, b -> a.length < b.length })

Функция max является функцией высшего порядка, потому что она принимает функцию в качестве второго аргумента. Этот второй аргумент является выражением, которое в свою очередь есть функция, то есть функциональный литерал. Как функция он эквивалентен объявлению:

fun compare(a: String, b: String): Boolean = a.length < b.length

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

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

fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
    var max: T? = null
    for (it in collection)
        if (max == null || less(max, it))
            max = it
    return max
}

Параметр less является (T, T) -> Boolean типом, то есть функцией, которая принимает два параметра типа T и возвращает Boolean: 'true', если первый параметр меньше второго.

В теле функции, строка 4, less используется как функция: она вызвана с двумя параметрами типа T.

Функциональный тип записывается, как указано выше, или может иметь именованые параметры, если нужно выявить смысл каждого из параметров.

val compare: (x: T, y: T) -> Int = ...

Синтаксис лямбда-выражений

Полная синтаксическая форма лямбда-выражений, таких как literals of function types, может быть представлена следующим образом:

val sum = { x: Int, y: Int -> x + y }

Лямбда-выражение всегда заключено в скобки {...}, объявление параметров при таком синтаксисе происходит внутри этих скобок и может включать в себя аннотации типов (опционально), тело функции начинается после знака ->. Если тип возвращаемого значения не Unit, то в качестве возвращаемого типа принимается последнее (а возможно и единственное) выражение внутри тела лямбды.

Если мы вынесем все необязательные объявления, то, что останется, будет выглядеть следующим образом:

val sum: (Int, Int) -> Int = { x, y -> x + y }

Обычное дело, когда лямбда-выражение имеет только один параметр. Если Kotlin может определить сигнатуру метода сам, он позволит нам не объявлять этот единственный параметр, и объявит его сам под именем it:

ints.filter { it > 0 } //Эта константа имеет тип '(it: Int) -> Boolean'

Мы можем явно вернуть значение из лямбды, используя qualified return синтаксис:

ints.filter {
    val shouldFilter = it > 0 
    shouldFilter
}

ints.filter {
    val shouldFilter = it > 0 
    return@filter shouldFilter
}

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

Анонимные функции

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

fun(x: Int, y: Int): Int = x + y

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

fun(x: Int, y: Int): Int {
    return x + y
}

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

ints.filter(fun(item) = item > 0)

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

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

Одним из отличий лямбда-выражений от анонимных функций является поведение оператора return (non-local returns). Слово return , не имеющее метки (@), всегда возвращается из функции, объявленной ключевым словом fun. Это означает, что return внутри лямбда-выражения возвратит выполнение к функции, включающей в себя это лямбда-выражение. Внутри анонимных функций оператор return, в свою очередь, выйдет, собственно, из анонимной функции.

Замыкания

Лямбда-выражение или анонимная функция (так же, как и локальная функция или object expression) имеет доступ к своему замыканию, то есть к переменным, объявленным вне этого выражения или функции. В отличае от Java, переменные, захваченные в замыкании, могут быть изменены:

var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

Литералы функций с объектом-приёмником

Kotlin предоставляет возможность вызывать литерал функции с указаным объектом-приёмником. Внутри тела литерала вы можете вызывать методы объекта-приёмника без дополнительных определителей. Это схоже с принципом работы расширений, которые позволяют получить доступ к членам объекта-приёмника внутри тела функции. Один из самых важных примеров использования литералов с объектом-приёмником это Type-safe Groovy-style builders.

Тип такого литерала — это тип функции с приёмником:

sum : Int.(other: Int) -> Int

По аналогии с расширениями, литерал функции может быть вызван так, будто он является методом объекта-приёмника:

1.sum(2)

Синтаксис анонимной функции позволяет вам явно указать тип приёмника. Это может быть полезно в случае, если вам нужно объявить переменную типа нашей функции для использования в дальнейшем.

val sum = fun Int.(other: Int): Int = this + other

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

class HTML {
    fun body() { ... }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()  // создание объекта-приёмника
    html.init()        // передача приёмника в лямбду
    return html
}


html {       // лямбда с приёмником начинается тут
    body()   // вызов метода объекта-приёмника
}