Синтаксис замыканий
В прошлом уроке каждую функцию для applyTwice объявляли отдельно: через func и с именем. Но если функция нужна один раз, давать ей имя неудобно. Это как вписывать рецепт бутерброда в кулинарную книгу, чтобы один раз отдать его другу. Удобнее написать рецепт прямо на карточке, которую вы отдаёте.
Функцию без имени, записанную прямо там, где она нужна, называют замыканием:
func applyTwice(_ f: (Int) -> Int, to x: Int) -> Int {
f(f(x))
}
let triple = { (x: Int) -> Int in
x * 3
}
print(triple(2)) // 6
print(applyTwice({ (x: Int) -> Int in x + 10 }, to: 1)) // 21Давайте разберём программу:
applyTwiceдважды вызывает функцию, которую ей передали.- Константа
tripleхранит замыкание, которое утраивает число. Вызывают его так же, как функцию:triple(2)даёт6. - В последней строке замыкание написано прямо в вызове
applyTwice, на месте первого аргумента. Оно прибавляет10, поэтому1превращается в11, а потом в21.
Из чего состоит замыкание
Запись { (x: Int) -> Int in x * 3 } — это функция без слова func и без имени. Её части:
- фигурные скобки — начало и конец замыкания;
(x: Int) -> Int— параметры и тип результата, как в заголовке функции;in— слово, которое отделяет заголовок от тела;x * 3— тело; если оно из одного выражения,returnне нужен, как у функции.
Тип у triple — (Int) -> Int. Замыкание — такое же значение, как функция: его кладут в константу, передают аргументом и возвращают из функции.
Короткие записи
Полная запись длинная, и Swift позволяет её сокращать. Сокращать удобнее всего там, где компилятор уже знает нужный тип функции, например в аргументе applyTwice или sorted(by:):
print(applyTwice({ x in x + 10 }, to: 1)) // 21
print(applyTwice({ $0 + 10 }, to: 1)) // 21
let numbers = [5, 2, 8, 1]
print(numbers.sorted(by: { a, b in a > b })) // [8, 5, 2, 1]
print(numbers.sorted { $0 > $1 }) // [8, 5, 2, 1]
print(numbers.sorted(by: >)) // [8, 5, 2, 1]Без типов
Параметр f у applyTwice имеет тип (Int) -> Int. Поэтому в замыкании можно не писать типы: { x in x + 10 }. Компилятор сам поймёт, что x — это Int и результат тоже Int. Это тот же вывод типов, что в let n = 5.
Имена $0 и $1
Можно не придумывать имена параметрам вовсе. Тогда первый параметр называется $0, второй — $1 и так далее, а заголовок вместе с in пропадает: { $0 + 10 }. Так пишут короткие замыкания в одну строку. В длинном замыкании свои имена параметров читать удобнее.
Замыкание после скобок
Метод sorted(by:) принимает функцию типа (Int, Int) -> Bool. Она получает два элемента и отвечает, должен ли первый стоять раньше второго. Разберём два вызова:
{ a, b in a > b }ставитaраньшеb, еслиaбольше. Получается порядок по убыванию.sorted { $0 > $1 }— то же замыкание, вынесенное за скобки вызова. Если замыкание — последний аргумент, его можно написать после закрывающей скобки. Здесь других аргументов нет, поэтому пропали и скобки, и меткаby:.
Оператор вместо замыкания
sorted(by: >) — самая короткая запись. Оператор > для чисел — тоже функция типа (Int, Int) -> Bool. Поэтому его передают по имени, как любую функцию.
Почему «замыкание»
Здесь картинка с карточкой неточна. Бумажная карточка ничего не знает о кухне, где её написали. А замыкание видит константы и переменные вокруг того места, где его написали:
let bonus = 5
print(applyTwice({ $0 + bonus }, to: 1)) // 11bonus — не параметр замыкания, а константа рядом с ним. Замыкание пользуется ею так же, как своим $0: 1 + 5 даёт 6, 6 + 5 даёт 11. Отсюда и название: замыкание замыкает в себе имена из окружающего кода.
Этот вопрос часто задают на собеседовании: что такое замыкание? Ответ из трёх частей:
- функция без имени, которую пишут прямо в коде, в фигурных скобках;
- её можно сохранить в константу и передать аргументом;
- внутри она видит константы и переменные вокруг себя.
Частая ошибка
В замыкании стало две строки, а return не написали:
let shout = { (word: String) -> String in
let loud = word.uppercased()
loud + "!" // ✗
}error: missing return in closure expected to return 'String'Программа не компилируется. Без return обходится только тело из одного выражения. Здесь в теле две строки, и последнюю Swift считает обычной инструкцией, а не результатом. Поэтому над ошибкой компилятор ещё и предупреждает, что результат + не используется. Допишите return: return loud + "!".
Главное
- Замыкание — функция без имени в фигурных скобках:
{ (x: Int) -> Int in x * 3 }; словоinотделяет заголовок от тела. - Там, где тип функции известен, типы можно не писать, а параметры называть
$0,$1. - Последний аргумент-замыкание можно вынести за скобки вызова:
numbers.sorted { $0 > $1 }. - Замыкание видит константы и переменные вокруг места, где его написали.