Functional Swift: Currying
Currying. It’s one of the stalwarts of functional programming.
Is Currying something you need to know about to be a developer?
Nope.
Is it handy to know about?
Yes. Think of it as a pattern for your toolbox. It can allow some elegant solutions and less code.
Is it weird and esoteric?
No; there’s a certain ‘shape’ to currying code but once you recognise it, there’s no mystery.
Do I need custom libs to do it?
Nope. You can quite easily roll-your-own currying in Swift (but of course you can get helper libs for it too).
Currying in the wild
A possible scenario
Let’s look at an example scenario and see how currying comes into play.
Suppose you are using a third-party text rendering library called AcmeRender
and you have no access to the source code.
Let’s mock up a method on the library for drawing text:
// Acme library mockup
struct AcmeRender {
public static func render(color: String, coord: CGPoint, text: String) {
print("TextLib: drawing '\(text)' in color \(color) at \(coord)")
}
}
And then we might use the Acme library like this:
func drawGreeting() {
AcmeRender.render(color: "red", coord: .init(x: 10, y: 20), text: "Hello")
AcmeRender.render(color: "red", coord: .init(x: 10, y: 40), text: "and")
AcmeRender.render(color: "red", coord: .init(x: 10, y: 60), text: "Goodbye")
}
This works fine. But that color:
parameter is a little annoying because we’re using "red"
on every line. Can we find a way to just specify "red"
once?
Here’s an obvious refactor that would tidy things up a bit:
func renderRedFunc(coord: CGPoint, text: String) {
AcmeRender.render(color: "red", coord: coord, text: text)
}
func drawGreeting() {
renderRedFunc(coord: .init(x: 10, y: 20), text: "Hello")
renderRedFunc(coord: .init(x: 10, y: 40), text: "and")
renderRedFunc(coord: .init(x: 10, y: 60), text: "Goodbye")
}
That’s a bit better, but not much. What if you want to draw a load of green labels too? Our solution now ends up with two helpers:
func renderRedFunc(coord: CGPoint, text: String) {
AcmeRender.render(color: "red", coord: coord, text: text)
}
func renderGreenFunc(coord: CGPoint, text: String) {
AcmeRender.render(color: "green", coord: coord, text: text)
}
So this solution doesn’t scale well1: we’ve still got quite a bit of repetition going on.
The core issue
What’s going on here? A source of friction is that AcmeRender.render
has a method that takes three parameters, some of which might change much less often than others (like color
). It would be nice to tease apart that method so we can call it in “two goes”: we would like to pass in a colour up-front, somehow, then later use that partial function call by providing the coordinate and text.
Sketching a solution
Let’s play fantasy programming and sketch some code for how we might want it to work:
let redRenderHelper = makeRenderHelper(color: "red")
redRenderHelper(coord: .init(x: 10, y: 20), text: "Hello")
redRenderHelper(coord: .init(x: 10, y: 40), text: "and")
redRenderHelper(coord: .init(x: 10, y: 60), text: "Goodbye")
So here we’re making a red text helper of some sort, then calling that helper directly with coordinates and text to do some rendering (in red).
This is a good starting point because we can use this sketch to flesh out a possible solution.
To start with, it looks like makeRenderHelper
is a func, so we can sketch it:
func makeRenderHelper(color: String) -> ??? {
...
}
We don’t have anything for the return type or the contents of the func yet.
What would the return type be? We can see from our code sketch that we use the returned thing (redRenderHelper
) like a func. That means we want to return a closure. And we call this closure with a CGPoint
and a String
for coord
and text
(and the closure doesn’t return anything).
So now we know that the closure has type (CGPoint, String) -> Void
2.
So now we can fill in the return type for makeRenderHelper
. It matches the type of our closure:
func makeRenderHelper(color: String) -> (CGPoint, String) -> Void {
...
}
And now we can start filling in the body of the func, because we know we need to return a closure with that signature:
func makeRenderHelper(color: String) -> (CGPoint, String) -> Void {
return { (coord: CGPoint, text: String) in
...
}
}
Now we just need to fill in the body of the closure we’re returning. It basically writes itself, because we know that when it is called it should call AcmeRender.render
with the colour, coord and text, which we have been given:
func makeRenderHelper(color: String) -> (CGPoint, String) -> Void {
return { (coord: CGPoint, text: String) in
AcmeRender.render(color: color, coord: coord, text: text)
}
}
And now we’ve achieved our goal. Here’s the complete example source demonstrating it (you can run it in a playground):
struct AcmeRender {
public static func render(color: String, coord: CGPoint, text: String) {
print("TextLib: drawing '\(text)' in color \(color) at \(coord)")
}
}
func makeRenderHelper(color: String) -> (CGPoint, String) -> Void {
// We're not bothering writing `return` below as it's not needed
// (as our func contains only one expression: the closure definition).
{ (coord: CGPoint, text: String) in
AcmeRender.render(color: color, coord: coord, text: text)
}
}
func drawGreeting() {
let redRender = makeRenderHelper(color: "red")
let greenRender = makeRenderHelper(color: "green")
// We've had to remove the `coord:` and `text:` labels
// from the calls below since we're callings closures now,
// which don't support labels
redRender(.init(x: 10, y: 20), "Hello")
redRender(.init(x: 10, y: 40), "and")
redRender(.init(x: 10, y: 60), "Goodbye")
greenRender(.init(x: 100, y: 20), "One")
greenRender(.init(x: 100, y: 40), "Two")
}
And this is essentially currying: given a function that takes three arguments (AcmeRender.render
), we pre-supply the first argument to make a new function
that takes the two remaining arguments.3
Notice how the call to AcmeRender
is using both the color
passed into the func and also coord
and text
passed into the closure. This is the essential magic here: we’re joining up data that was passed in at different places and times in the code into one call to our library.
Notice how in the above code we don’t supply argument labels like coord:
or text:
in calls to redRender
and greenRender
. This is because we’re now calling closures, not functions, and closures don’t support external parameter labelling.
I don’t think it’s a big deal here, but it’s a fine detail of closures that’s worth being aware of.
Afterthought: why was currying useful with AcmeRenderer?
Our pretend rendering library doesn’t store any state. You give it all the data it needs whenever you call it! There’s a certain purity about this, because it makes it harder to make mistakes – there’s no stored state in the library for developer to be confused about. However it can result in more verbose code.
If we addressed this using non-currying techniques, we might make a wrapper for the lib that stored some state such as color
. This would result in shorter code at the call sites to the library, but then we might be tempted to make the colour in the helper mutable. And if you passed around your library helper there’s then scope to make mistakes: code that uses it might make incorrect assumptions about its state.
The currying version also adds statefulness, but it’s immutable (e.g. always draws red). I think this adds some explicitness because it’s natural to create a good name like redRender
at the point you do a currying step to make the helper. And notice that we could then doing further currying on redRender
itself (or any colour variant), to make e.g. redOriginRender
that draws red text at coordinates (0, 0)
. So redOriginRender
would just take a string.
Next time: a currying helper using generics
Above we wrote a makeRenderHelper
func that does the currying.4
If we wanted to do more currying, we’d end up writing new funcs for each specific way we wanted to do it (e.g. the redOriginRender
example above).
Is there a tidier way to do this?
There is. We can use swift generics to write a helper to allow us to create curried functions on-the-fly without writing a new func for each situation.
More on this next time.
this isn’t necessarily a bad solution, of course; context always matters ↩︎
although Swift accepts you writing
-> ()
to mean ‘no return type’ (in factVoid
aliases to()
), you should be using-> Void
for clarity and consistency with Apple’s own APIs ↩︎I’m not going to split hairs in the main text, but technically there’s partial function application (PFA) and there’s currying, which are closely related: currying is taking a function with \(N\) params and making a chain of functions-returning-functions that all take one parameter, whereas partial application is taking a function with \(N\) params, pre-supplying \(M\) of the params, and returning a func that takes the remaining \( N - M \) params. So currying is a certain use case of PFA ↩︎
the name could be a little more descriptive though. Exercise: come up with a better name ↩︎