Статьи

Функция каррирования в Swift

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

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

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

Откройте Xcode, создайте новую игровую площадку для iOS или OS X и добавьте в нее следующий код:

01
02
03
04
05
06
07
08
09
10
class Car {
    var speed = 0
     
    func accelerateBy(factor: Int) -> Int {
        speed += factor
        return speed
    }
}
 
let car1 = Car()

Мы определяем базовый класс с одним свойством и одним методом или функцией экземпляра. Мы также создаем экземпляр класса, car1 . Мы можем вызвать метод accelerateBy(_:) нашего экземпляра Car с помощью следующего кода:

1
car1.accelerateBy(10)

Однако существует другой способ выполнения этого метода с использованием функции каррирования. В приведенном выше примере вы вызываете метод непосредственно на car1 несмотря на то, что метод фактически определен в классе Car . Метод, который мы написали, не относится к экземпляру car1 , а скорее к классу Car . Когда вы вызываете этот метод, в действительности происходит то, что Swift сначала переходит в класс Car , получает метод accelerateBy(_:) , сообщает методу, какой экземпляр используется, а затем выполняет измененный метод, специфичный для этого экземпляра. Чтобы показать вам, как это работает, добавьте следующую строку на игровую площадку:

1
Car.accelerateBy(car1)

Здесь мы обращаемся к методу accelerateBy(_:) класса Car и передаем в качестве параметра экземпляр, для которого мы хотим, чтобы эта функция выполнялась. На боковой панели вашей игровой площадки вы можете видеть, что результатом этого метода является другая функция.

Новая функция возвращается

Этот результат (Function) представляет собой новый метод accelerateBy(_:) специфичный для экземпляра car1 . Эта возвращаемая функция уникальна тем, что она ссылается на свойство speed car1, а не на общий метод, который вы определили ранее, который может ссылаться на свойство speed любого экземпляра Car .

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

1
Car.accelerateBy(car1)(10)

С помощью этого кода мы передаем 10 в качестве параметра в уникальный метод accelerateBy(_:) и в результате получаем текущую скорость car1 .

Функция карри на работе

Поздравляем! Вы только что впервые воспользовались функцией каррирования в Swift.

Помимо объединения нескольких функций, результат F unction, который возвращает ваш код, также может быть сохранен в переменной. Это позволяет вам хранить и быстро использовать метод accelerateBy(_:) специфичный для вашего экземпляра car1 . Добавьте следующие строки на вашу игровую площадку:

1
2
3
4
let a = Car.accelerateBy(car1)
a(10)
a(20)
a(30)

Вы можете видеть, что переменная a теперь ведет себя как любая другая глобально определенная функция. Преимущество заключается в том, что он специфичен для конкретного экземпляра Car , который можно определить во время выполнения, а не во время компиляции. На вашей игровой площадке вы можете увидеть ожидаемые результаты, отображаемые на боковой панели.

Результаты из сохраненной функции

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

1
2
3
func someFunction(input: AnyObject) -> (AnyObject) -> AnyObject {
    // Do stuff to return a function
}

Мы определяем функцию someFunction(_:) , которая принимает параметр AnyObject и возвращает другую функцию, которая также принимает параметр AnyObject который возвращает результат AnyObject . На первый взгляд определение функций такого типа может показаться очень запутанным и пугающим. Чтобы упростить его, мы можем воспользоваться typealias словом typealias в Swift, чтобы сопоставить новое имя с любым типом данных.

Добавьте следующий код на вашу игровую площадку:

1
2
3
4
5
6
7
typealias IntFunction = (Int) -> Int
func accelerationForCar(car: Car) -> IntFunction {
    return Car.accelerateBy(car)
}
 
let newA = accelerationForCar(car1)
newA(10)

Используя typealias для сопоставления имени IntFunction с типом данных (Int) -> Int , мы значительно упростили определение функции accelerationForCar(_:) . Тип данных (Int) -> Int просто представляет функцию, которая принимает параметр Int и возвращает значение Int .

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

Хотя встроенная функция каррирования в Swift очень полезна, вы можете создать свои собственные функции, не связанные с классами. Для этой части урока мы сначала будем использовать пример функции, которая умножает другое число на постоянное значение.

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

1
2
3
func multiplyBy5(a: Int) -> Int {
    return a * 5
}

Теперь представьте, что вам нужна похожая функция, но вам нужно, чтобы она умножалась на 10, а не на 5. Затем вам нужна еще одна функция, чтобы умножить на 20. Хотя вы можете создать три одинаковые функции и назвать их multiplyBy5 , multiplyBy10 и multiplyBy20 , это может быть обработанным намного лучше, используя функцию каррирования.

Добавьте следующий фрагмент кода на свою игровую площадку:

01
02
03
04
05
06
07
08
09
10
11
func multiplyBy(a: Int) -> IntFunction {
    func nestedMultiply(b: Int) -> Int {
        return a * b
    }
     
    return nestedMultiply
}
 
multiplyBy(10)(20)
let multiplyBy5 = multiplyBy(5)
multiplyBy5(4)

Мы определяем функцию multiplyBy(_:) которая принимает Int как единственный параметр и возвращает функцию типа IntFunction , типа данных, который мы определили ранее в этом руководстве. В этой функции мы определяем другую функцию nestedMultiply(_:) . Мы вложили эту функцию в первую, чтобы она не могла быть выполнена вне области действия функции multiplyBy(_:) и имела доступ к входному параметру.

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

Пользовательские функции каррирования результатов

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

01
02
03
04
05
06
07
08
09
10
11
12
13
func add(a: Int) -> IntFunction {
    return { b in a + b }
}
 
let add2 = add(2)
add2(4)
 
func subtract(a: Int)(b: Int) -> Int {
    return b — a
}
 
let subtract5 = subtract(5)
subtract5(b: 8)

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
func multiply(a: Int, b: Int, c: Int) -> Int {
    return a * b * c
}
 
func multiply(a: Int) -> (Int) -> IntFunction {
    func nestedMultiply1(b: Int) -> IntFunction {
        func nestedMultiply2(c: Int) -> Int {
            return a * b * c
        }
         
        return nestedMultiply2
    }
     
    return nestedMultiply1
}
 
multiply(4, 5, 6)
multiply(4)(5)(6)

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

Каррирование функций в Swift — сложная концепция для понимания, но по своей сути она состоит из двух ключевых концепций:

  • создание функций, которые возвращают другие функции
  • сведение функций с несколькими аргументами в ряд функций, каждая с одним аргументом

Существует множество способов объединить функции в карри, и, хотя в этом руководстве использовался только тип данных Int , одни и те же процессы можно использовать с любым типом данных в проектах Swift. Это позволяет хранить функции в переменных и многократно использовать их в коде.

Как всегда, пожалуйста, оставляйте свои комментарии и отзывы ниже в комментариях.