Перейти к содержимому
Теория · Замыкания · урок 1 из 8

Функции как значения

Чему вы научитесь

Чему вы научитесь:

  • хранить функцию в переменной, передавать её аргументом и возвращать из другой функции;
  • записывать замыкания коротко: без типов, с $0 и после скобок вызова;
  • обрабатывать массивы методами map, filter, reduce и compactMap;
  • объяснять, как замыкание захватывает переменную и почему две константы могут делить один счётчик.
Карточка рецепта { } с прикреплённой запиской count 3 входит в машину map, на выходе банки

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

В теме о функциях вы вызывали функцию сразу: писали имя, скобки и аргументы. Но с функцией можно обращаться и как с карточкой. Имя функции без скобок — сама карточка, а скобки после имени означают «приготовить сейчас».

Вот функция, которую положили в переменную:

SWIFT
func double(_ x: Int) -> Int {
    x * 2
}

func square(_ x: Int) -> Int {
    x * x
}

var transform: (Int) -> Int = double
print(transform(5)) // 10
transform = square
print(transform(5)) // 25

Давайте разберём программу по шагам:

  1. Функции double и square принимают Int и возвращают Int.
  2. var transform: (Int) -> Int = double кладёт в переменную саму функцию double. Скобок после double нет, поэтому функция не вызывается.
  3. transform(5) вызывает то, что лежит в переменной, и печатает 10.
  4. transform = square кладёт в ту же переменную другую функцию.
  5. Тот же вызов transform(5) теперь печатает 25.

Тип функции

У каждого значения в Swift есть тип, и у функции тоже. Тип функции записывают так: типы параметров в круглых скобках, стрелка и тип результата. Запись (Int) -> Int состоит из трёх частей:

  • (Int) — функция принимает одно целое число;
  • -> — стрелка, как в заголовке функции;
  • Int — функция возвращает целое число.

Функции double и square делают разное, но тип у них один, поэтому обе помещаются в transform. Вот ещё три типа:

  • (Int, Int) -> Int — функция принимает два числа и возвращает число;
  • (String) -> Void — функция принимает строку и ничего не возвращает;
  • () -> Int — функция без параметров возвращает число.

Меток аргументов в типе нет. Если положить функцию add(a:b:) в константу op типа (Int, Int) -> Int, вызывают её без меток: op(2, 3). Вызов op(a: 2, b: 3) не скомпилируется.

В переменную помещается только функция подходящего типа. Функция, которая принимает строку, в transform не попадёт: компилятор сообщит, что типы не совпадают.

Функция как параметр

Раз функция — значение, её можно передать в другую функцию аргументом. Это как отдать карточку другу: он приготовит блюдо по вашему рецепту столько раз, сколько нужно.

SWIFT
func applyTwice(_ f: (Int) -> Int, to x: Int) -> Int {
    f(f(x))
}

print(applyTwice(double, to: 3)) // 12
print(applyTwice(square, to: 3)) // 81

Разберём первый вызов:

  1. applyTwice получает функцию double в параметр f и число 3 в параметр x.
  2. Внутренний вызов f(x) — это double(3), он даёт 6.
  3. Внешний вызов f(6) даёт 12, и программа его печатает.

Во втором вызове в f лежит square: сначала 3 * 3, потом 9 * 9, то есть 81.

Функцию, которая принимает другую функцию или возвращает её, называют функцией высшего порядка. Работу здесь делят двое: applyTwice решает, сколько раз и с каким числом вызвать f, а переданная функция решает, что с этим числом сделать.

Функция как результат

Функцию можно и вернуть из другой функции. Тогда после стрелки в заголовке стоит тип функции:

SWIFT
func pick(big: Bool) -> (Int) -> Int {
    if big {
        return square
    }
    return double
}

let chosen = pick(big: true)
print(chosen(4)) // 16
print(pick(big: false)(4)) // 8

Заголовок func pick(big: Bool) -> (Int) -> Int читают так: функция pick принимает Bool и возвращает функцию типа (Int) -> Int. Разберём вызовы:

  1. pick(big: true) возвращает не число, а функцию square, и она попадает в константу chosen.
  2. chosen(4) вызывает square и печатает 16.
  3. pick(big: false)(4) сначала получает функцию double, а вторые скобки тут же вызывают её с числом 4. Получается 8.

На собеседовании часто говорят: в Swift функции — значения первого класса. Это значит, что с функцией можно сделать то же, что с числом или строкой:

  • положить в переменную или константу;
  • передать аргументом в другую функцию;
  • вернуть из функции.

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

Частая ошибка

Функцию передали со скобками:

SWIFT
print(applyTwice(double(3), to: 3)) // ✗
TEXT
error: cannot convert value of type 'Int' to expected argument type '(Int) -> Int'

Программа не компилируется. Скобки после double вызывают функцию прямо на месте, и double(3) даёт число 6. А параметр f ждёт функцию типа (Int) -> Int, а не Int. Это всё равно что отдать другу не карточку, а уже готовое блюдо. Передавайте имя без скобок: applyTwice(double, to: 3).

Главное

  • Функция — значение: её кладут в переменную, передают аргументом и возвращают из другой функции.
  • Тип функции записывают как (Int) -> Int: типы параметров в скобках, стрелка и тип результата; меток аргументов в типе нет.
  • Функция высшего порядка принимает другую функцию или возвращает её.
  • Имя без скобок — сама функция, а имя со скобками — её вызов и результат.