Swift Function Currying
Delicious partial function application
If you aren't familiar with Function Currying, the concept is fairly simple. Imagine you have some method that takes 4 arguments. If the function is curried, then the first function takes argument #1 and returns a function that takes argument #2. That function takes argument #2 and returns a function taking argument #3. This continues until you provide the final argument in the list and the function's actual result is returned. You are essentially breaking the function up such that each part only takes one argument, wrapping that argument in a closure, then returning the next function in the chain.
First I'll show you how to do this manually, then we'll let swiftc do the work. Finally we wrap up with some more realistic examples beyond the ubiquitous add two integers.
The Hard Way
The most difficult and annoying way to curry functions is by manually nesting enclosed functions.
func takeFive<T:IntegerArithmeticType>(a:T) -> (T->(T->(T->(T->T)))) {
func takeFour(b:T) -> (T->(T->(T->T))) {
func takeThree(c:T) -> (T->(T->T)) {
func takeTwo(d:T) -> (T->T) {
func takeOne(e:T) -> T {
return a + b + c + d + e
}
return takeOne
}
return takeTwo
}
return takeThree
}
return takeFour
}
let addFiver = takeFive(5)
addFiver(4)(3)(2)(1) // == 15
let addTwelve = addFiver(4)(3)
addTwelve(2)(1) // == 15
Notice that we re-used the addFiver
function in two locations without having to specify the argument 5
multiple times. The nested functions capture local variables automatically in the evaluation environment. Paste this in to a playground and you can see that the result are as expected.
If this all sounds like greek to you, I highly suggest doing the Structure and Interpretation of Computer Programs course. It will make you a better programmer (seriously).
If you want a lighter introduction, MIT Open Courseware has posted the SICP lecture series on youtube. The video series is old, shot when HP brought Abelson and Sussman in to teach some of their developers but it's a fantastic look at how programming languages are built and the principals are still extremely relevant. Plus they dress up in wizard hats.
Naming is also Hard
Naming things can often be extremely difficult. Why should we bother giving names to those nested functions?
The other annoying issue is the return types; the more parameters you have, the more annoying the return type declarations become, but the rule is actually simple once you understand it:
An outer function needs one (T->
for every function it encloses, ending with the return type T
and enough parens to close things out.
Since takeOne
doesn't enclose any functions, it only has the return type left.
takeTwo
encloses a single function so we get (T->
plus the return and a matching paren T)
to give us (T->T)
.
takeFive
encloses four functions so it gets four (T->
, giving (T->(T->(T->(T->T))))
.
Still, let's just use anonymous functions (aka closures) and let the compiler infer the return types.
func takeFive<T:IntegerArithmeticType>(a:T) -> (T->(T->(T->(T->T)))) {
return { (b:T) in
return { (c:T) in
return { (d:T) in
return { (e:T) in
return a + b + c + d + e
}
}
}
That's a big improvement. We can't get rid of the outermost return type declaration and it involves a lot of boilerplate, but it certainly is simpler.
The Easy Way
Now forget all that stuff we just discussed because Swift supports automatic function currying. Just separate the parameters inside their own parens and you're done:
func takeFive<T:IntegerArithmeticType>(a:T)(b:T)(c:T)(d:T)(e:T) -> T {
return a + b + c + d + e
}
Note: There appears to be a bug currently with generic curried functions in some cases that causes you to get different results depending on whether you call it with all parameters immediately or take advantage of partial application. I've filed a radar on it and hopefully it will be fixed. In the mean time, make sure you use specific types on your curried functions and all should be well.
Most examples are too simple to be useful
Great, we can curry functions. Why should we care?
It allows us to control state or ambient contexts without resorting to actual mutable global state or creating boilerplate objects.
A caller might ask us for a function that runs queries against a certain database. We can use a curried function (possibly private) that takes connection info as its first parameter, followed by query information as its second; the caller gets back a curried function that takes only the query parameters. The caller can then use this returned function without ever being aware of the details. We don't have to create queue or thread specific ambient context information to track some sort of "current" connection. The storage lifetime of the connection information is tied to the lifetime of the closure's environment. When the closure goes away, so too will the saved context. This also lets us have multiple active connections or pass them between queues and threads. In short, we can hide the details without jumping through any hoops or writing boilerplate objects just to carry some state data around.
edit: Ole Begemann (by way of objc.io ) points out that Swift instance methods are also curried functions. You can get a reference to those curried functions with a simple let f = MyClass.someFunction
. Now f
will be a function that takes an instance of MyClass
and returns a function matching the someFunction
signature.
Conclusion
Function currying is another one of those fun tools in the toolbox; use it where appropriate.
Coming Soon: The next major effort-post; I'm walking through how I did Horrible Things, from dumping symbols to disassembly to trying to guess method parameters. Fun for the whole family!
This blog represents my own personal opinion and is not endorsed by my employer.