Introduction
Currying is a feature found in most modern programming languages. It translates a single function with multiple arguments into a series of functions each with one argument. In essence, this enables for storing functions in variables and creating functions that return functions.
While it may seem like a strange concept at first, it is a powerful technique that can be very useful at times. In this tutorial, I am going to show you how to take advantage of function currying in Swift.
1. Function Currying with Classes
Before defining our own custom functions, I am first going to show you a simple example of currying in Swift using classes.
Open Xcode, create a new iOS or OS X playground, and add the following code to it:
class Car { var speed = 0 func accelerateBy(factor: Int) -> Int { speed += factor return speed } } let car1 = Car()
We define a basic class with one property and one instance method or function. We also create an instance of the class, car1
. We can call the accelerateBy(_:)
method of our Car
instance with the following code:
car1.accelerateBy(10)
There is, however, another way of executing this method through the use of function currying. In the above example, you call the method directly on car1
despite the method actually being defined in the Car
class. The method we have written is not specific to the car1
instance, but rather the Car
class. When you call this method, what is really happening is that Swift first goes to the Car
class, retrieves the accelerateBy(_:)
method, tells the method which instance is being used, and then executes an altered method specific to that instance. To show you how this works, add the following line to your playground:
Car.accelerateBy(car1)
What we are doing here is accessing the accelerateBy(_:)
method of the Car
class and passing, as a parameter, which instance we want this function to be executed on. In your playground's sidebar, you can see that the result of this method is another function.
What this (Function) result represents is actually a new accelerateBy(_:)
method specific to the car1
instance. This returned function is unique in that it specifically references car1
's speed
property rather than the generic method you defined earlier, which can reference the speed
property of any Car
instance.
As you would expect, we can pass new parameters to this returned function in order to execute it as we usually do. Add the following line of code to your playground:
Car.accelerateBy(car1)(10)
With this code, we pass 10
as a parameter into the unique accelerateBy(_:)
method and get car1
's current speed returned as a result.
Congratulations! You just took advantage of function currying in Swift for the very first time.
In addition to currying together multiple functions, the Function result that your code is returning can also be stored into a variable. This allows you to store and quickly reuse the accelerateBy(_:)
method specific to your car1
instance. Add the following lines to your playground:
let a = Car.accelerateBy(car1) a(10) a(20) a(30)
You can see that variable a
now behaves like any other globally defined function. The advantage is that it is specific to a particular Car
instance, which can be defined at run time rather than compile time. In your playground, you can see the expected outputs being displayed in the sidebar.
Lastly, I am going to show you how you can return a function from another function by reimplementing the behavior of the Car
class that we have just looked at. We are going to do this by defining a function that returns another function. In your code, this function could look something like this:
func someFunction(input: AnyObject) -> (AnyObject) -> AnyObject { // Do stuff to return a function }
We define the someFunction(_:)
function, which accepts an AnyObject
parameter and returns another function that also accepts an AnyObject
parameter that returns an AnyObject
result. This type of function definition can look very confusing and daunting at first. To simplify it, we can take advantage of the typealias
keyword in Swift to map a new name to any data type.
Add the following code to your playground:
typealias IntFunction = (Int) -> Int func accelerationForCar(car: Car) -> IntFunction { return Car.accelerateBy(car) } let newA = accelerationForCar(car1) newA(10)
By using typealias
to map the name of IntFunction
to the (Int) -> Int
data type, we have greatly simplified the accelerationForCar(_:)
function definition. The (Int) -> Int
data type simply represents a function that accepts an Int
parameter and returns an Int
value.
This new function uses the built-in behavior of the Car
class to return an IntFunction
object that can then be stored in a variable and used like we did before.
2. Currying Custom Functions
While the built-in function currying behavior in Swift is very useful, you may wish to create your own functions that aren't related to classes. For this part of the tutorial, we are first going to use the example of a function that multiplies another number by a constant value.
Imagine that you want to create a function that accepts a single input number and multiplies that by 5. This simple function could look like this:
func multiplyBy5(a: Int) -> Int { return a * 5 }
Now imagine that you need a similar function, but you need it to multiply by 10 rather than 5. Then you need another function to multiply by 20. While you could create three similar functions and name them multiplyBy5
, multiplyBy10
, and multiplyBy20
, this could be handled much better using function currying.
Add the following code snippet to your playground:
func multiplyBy(a: Int) -> IntFunction { func nestedMultiply(b: Int) -> Int { return a * b } return nestedMultiply } multiplyBy(10)(20) let multiplyBy5 = multiplyBy(5) multiplyBy5(4)
We define the multiplyBy(_:)
function that accepts an Int
as its only parameter and returns a function of type IntFunction
, the data type we defined earlier in this tutorial. In this function, we define another function, nestedMultiply(_:)
. We nest this function within the first so that it cannot be executed outside of the scope of the multiplyBy(_:)
function and has access to the a
input parameter.
The three lines below the function definition are simple examples of how you can curry the functions together.
While you can create functions for currying using nested functions, you can also create them using closures or by defining multiple sets of parameters. As an example, add the following code snippet to your playground:
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)
As you can see, these two new functions can be curried together in the same way as all the other functions have been throughout this tutorial. The only exception is that, with the function defined with two sets of parameters, you must explicitly name the second parameter.
There is no limit to the number of levels of function currying you can implement within your code. The following code shows an example of three curried functions and how it differs over a single function with three parameters.
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)
The main difference between these two multiplication functions is that the curried version can effectively be paused and stored in a variable after the first or second parameter has been processed. This makes the function very easy to reuse and pass around as an object.
Conclusion
Function currying in Swift is a difficult concept to grasp, but, at its core, it's basically about two key concepts:
- creating functions that return other functions
- reducing functions with multiple arguments into a series of functions each with one argument
There are a variety of ways to curry functions together and, while only the Int
data type was used in this tutorial, the same processes can be used with any data type in your Swift projects. This enables for functions to be stored in variables and be used many times throughout your code.
As always, please be sure to leave your comments and feedback below in the comments.
Comments