Основы Swift / 8.3. Функциональные типы


Видео


Функциональные типы
Каждая функция имеет свой специальный тип, созданный из типов параметров и возвращаемого типа функции:
@{8.3\1}
func addTwoInts(a: Int, _ b: Int) -> Int {
    return a + b
}
func multiplyTwoInts(a: Int, _ b: Int) -> Int {
    return a * b
}
Этот пример определяет две простые математические функции с названиями addTwoInts и multiplyTwoInts. Обе эти функции принимают два значения типа Int и возвращают значение типа Int, которое есть результат выполнения подходящей математический операции.

Тип обеих функций - (Int, Int) -> Int. Это можно прочитать как:

"Функция, которая имеет два параметра, оба с типом Int, и которая возвращает значение типа Int."
@{8.3\2}
func printHelloWorld() {
    print("я без ума от этого курса")
}
Здесь приведён ещё один пример для функции без параметров или возвращаемого значения.

Тип этой функции () -> Void или "функция, которая не имеет параметров и возвращает Void".
Использование типов функций
Вы можете использовать функциональные типы, как и любые другие типы в Swift. Например, Вы можете определить константу или переменную функционального типа и присвоить подходящую функцию этой переменной:
@{8.3\3}
func addTwoInts(a: Int, _ b: Int) -> Int {
    return a + b
}
var mathFunction: (Int, Int) -> Int = addTwoInts
"Определить переменную под названием mathFunction, которая имеет тип функции, которая принимает два значения типа Int и возвращает значение типа Int. Установить этой новой функции ссылку на функцию под названием addTwoInts."

Фнукция addTwoInts(_:_:) имеет тот же тип, что и переменная mathFunction, так что это присвоение будет разрешено системой проверки типов Swift.
@{8.3\4\1}
func addTwoInts(a: Int, _ b: Int) -> Int {
    return a + b
}
var mathFunction: (Int, Int) -> Int = addTwoInts
print("Результат: \(mathFunction(2, 3))")
// Выведет "Результат: 5"
Теперь Вы можете вызвать присвоенную функцию с помощью имени mathFunction.
@{8.3\4\2}
Другая функция с тем же типом может быть присвоена той же переменной, как и в случае с нефункциональными типами.
func multiplyTwoInts(a: Int, _ b: Int) -> Int {
    return a * b
}
mathFunction = multiplyTwoInts
print("Результат: \(mathFunction(2, 3))")
// Выведет "Результат: 6"
Автовыведение типа
Как и любой другой тип, Вы можете не указывать тип тут и оставить на откуп Swift вывести тип присваиваемой функции константе или переменной.
@{8.3\5}
func addTwoInts(a: Int, _ b: Int) -> Int {
    return a + b
}
let anotherMathFunction = addTwoInts
// для переменной anotherMathFunction выведен тип (Int, Int) -> Int
Функциональные типы как типы параметров
Вы можете использовать функциональные тип наподобие (Int, Int) - > Int в качестве типа параметра для другой функции. Это позволяет Вам оставить часть аспектов реализации функции на то, что её вызывает, для предоставления их в момент вызова.
@{8.3\6}
func addTwoInts(a: Int, _ b: Int) -> Int {
    return a + b
}
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
    print("Результат: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// Выведет "Результат: 8"
Здесь приведён пример распечатки результатов математических функций, данных выше.

Этот пример определяет функцию printMathResult(_:_:_:), которая имеют три параметра. Первый параметр называется mathFunction и имеет тип (Int, Int) -> Int. Вы можете передать любую функцию такого типа в качестве аргумента для первого параметра. Второй и третий параметр называются a и b, и оба имеют тип Int. Они используются в качестве двух входных значений для предоставляемой математической функции.

Когда вызывается функция printMathResult(_:_:_:), то она принимает функцию addTwoInts(_:_:), а также целые числа 3 и 5. Она в свою очередь вызывает предоставленную функцию со значениями 3 и 5, а потом выводит её результат - 8.

Роль функции printMathResult(_:_:_:) - напечатать результат вызова математической функции подходящего типа. Абсолютно неважно, что на самом деле делает имплементация функции, важно лишь, чтобы функция была корректного типа. Это позволяет printMathResult(_:_:_:) вынести часть своей функциональности на сторону вызывающего функцию в типобезопасном виде.
Функциональные типы как возвращаемые значения
Вы можете использовать функциональный тип в качестве возвращаемого значения из другой функции. Вы можете сделать это, написав полный тип функции сразу после стрелки-возврата (->) у возвращающей функции.
@{8.3\7\1}
func stepForward(_ input: Int) -> Int {
    return input + 1
}

func stepBackward(_ input: Int) -> Int {
    return input - 1
}
Пример определяет две простых функции с названиями stepForward(_:) и stepBackward(_:). Функция stepForward(_:) возвращает значение, на 1 большее чем входное, а функция stepBackward(_:) возвращает значение, на 1 меньшее входного. Обе функции имеют тип (Int) -> Int.
@{8.3\7\2}
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    return backward ? stepBackward : stepForward
}
Здесь функция с названием chooseStepFunction(backward:), чей тип возвращаемого установлен в (Int) -> Int. Функция chooseStepFunction(backward:) возвращает или функцию stepForward(_:), или функцию stepBackward(_:), основываясь на Булевом параметре с названием backward.
@{8.3\7\3}
var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero теперь ссылается на функцию stepBackward()
Теперь Вы можете использовать chooseStepFunction(backward:) для получения функции, которая будет выполнять перемещение в одном или другом направлении.

Предшествующий пример определяет, положительный или отрицательный шаг нужен для переменной под названием currentValue для приближения к нулю. currentValue имеет изначальное значение 3, что значит, что currentValue > 0 вернёт true, заставив chooseStepFunction(backward:) вернуть stepBackward(_:). Ссылка на вернувшуюся функцию будет храниться в константе с названием moveNearerToZero.
@{8.3\7\4}
print("Считаем до нуля:")
// Считаем до нуля:
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("ноль!")
// 3...
// 2...
// 1...
// ноль!
Так как теперь moveNearerToZero ссылается на корректную функцию, то она может быть использована для счёта в направлении нуля.
Вложенные функции
Все функции, которые Вы встречали ранее в этой главе, были примерами глобальных функций, определённых в глобальном пространстве. Вы можете также определить функции внутри тел других функций, что известно как вложенные функции.

Вложенные функции скрыты от внешнего мира по-умолчанию, но они всё также могут быть вызваны и использованы их заключающей функцией. Заключающая функция также может вернуть одну из своих вложенных функций, чтобы позволит вложенной функции быть использованной в другом пространстве.
@{8.3\8}
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    func stepForward(_ input: Int) -> Int { return input + 1 }
    func stepBackward(_ input: Int) -> Int { return input - 1 }
    return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero теперь ссылается на вложенную функцию stepForward()
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("ноль!")
// -4...
// -3...
// -2...
// -1...
// ноль!
Вы можете переписать пример chooseStepFunction(backward:) выше для использования и возвращения вложенных функций.
Ранний выход
Инструкций guard, как и инструкция if, выполняет инструкции, основываясь на Булевом значении выражения. Вы можете использовать инструкцию guard для требования того, что условие должно быть истинным, чтобы код после инструкции guard был выполнен. В отличие от инструкции if, инструкция guard всегда имеет блок else - код внутри блока else будет выполнен, если условие неистинно.
@{8.3\9}
func greet(person: [String : String]) {
    guard let name = person["name"] else {
        return
    }

    print("Привет, \(name)!")

    guard let location = person["location"] else {
        print("Надеюсь погода хороша рядом с Вами.")
        return
    }

    print("Надеюсь погода хороша в \(location).")
}

greet(person: ["name": "John"])
// Выведет "Привет, John!"
// Выведет "Надеюсь погода хороша рядом с Вами."
greet(person: ["name": "Jane", "location": "Cupertino"])
// Выведет "Привет, Jane!"
// Выведет "Надеюсь погода хороша в Cupertino.”
Если условие инструкции guard выполнено, то выполнение кода продолжается после закрывающей фигурной скобки инструкции guard. Любые переменные или константы, получившие значения с использованием опционального связывания в части условия, будут доступны на протяжении блока кода, в котором присутствует инструкция guard.

Если условие не выполняется, то будет выполнен код внутри ветви else. Эта ветвь должна передать управление на выход из блока кода, в котором присутствует инструкция guard. Это можно сделать с помощью с инструкций передачи управления, таких как: return, break, continue или throw; а так же с помощью вызова функции или метода, который не осуществляет возврат управления, такого как например fatalError(_:file:line:).

Использование инструкции guard для требований улучшает читабельность кода сравнительно такой же реализации с помощью инструкции if. Это позволяет Вам писать код, который фактически выполняется без оборачивания в блок else, и это позволяет Вам держать код, который обрабатывает нарушение требований рядом с самими требованиями.