Swift Function Currying

06 Nov, 2014
Xebia Background Header Wave

One of the lesser known features of Swift is Function Currying. When you read the Swift Language Guide you won't find anything about curried functions. Apple only describes it in their Swift Language Reference. And that's a pity, since it's a very powerful and useful feature that deserves more attention. This post will cover the basics and some scenarios in which it might be useful to use curried functions.

I assume you're already somewhat familiar with function currying since it exists in many other languages. If not, there are many articles on the Internet that explain what it is and how it works. In short: you have a function that receives one or more parameters. You then apply one or more known parameters to that function without already executing it. After that you get a function reference to a new function that will call the original function with the applied parameters. One situation in which I find it useful to use curried functions is with completion handlers. Imagine you have a function that makes a http request and looks something like this: [objc] func doGET(url: String, completionHandler: ([String]?, NSError?) -> ()) { // do a GET HTTP request and call the completion handler when receiving the response } [/objc] This is a pretty common pattern that you see with most networking libraries as well. We can call it with some url and do a bunch of things in the completion handler: [objc] doGET("", completionHandler: { results, error in self.results = results self.resultLabel.text = "Got all items" self.tableView.reloadData() }) [/objc] The completion handler can become a lot more complex and you might want to reuse it in different places. Therefore you can extract that logic into a separate function. Luckily with Swift, functions are just closures so we can immediately pass a completion handler function to the doGET function: [objc] func completionHandler(results: [String]?, error: NSError?) { self.results = results self.resultLabel.text = "Got all items" self.tableView.reloadData() } func getAll() { doGET("", completionHandler) } func search(search: String) { doGET("" + search, completionHandler) } [/objc] This works well, as long as the completion handler should always do exactly the same thing. But in reality, that's usually not the case. In the example above, the resultLabel will always display "Got all items". Lets change that into "Got searched items" for the search request: [objc] func search(search: String) { doGET("" + search, {results, error in self.completionHandler(results, error: error) self.resultLabel.text = "Got searched items" }) } [/objc] This will work, but it doesn't look very nice. What we actually want is to have this dynamic behaviour in the completionHandler function. We can change the completionHandler in such a way that it accepts the text for the resultLabel as a parameter and then returns the actual completion handler as a closure. [objc] func completionHandler(text: String) -> ([String]?, NSError?) -> () { return {results, error in self.results = results self.resultLabel.text = text self.tableView.reloadData() } } func getAll() { doGET("", completionHandler("Got all items")) } func search(search: String) { doGET("" + search, completionHandler("Got searched items")) } [/objc] And as it turns out, this is exactly what we can also do using currying. We just need to add the parameters of the actual completion handler as a second parameters group to our function: [objc] func completionHandler(text: String)(results: [String]?, error: NSError?) { self.results = results self.resultLabel.text = text self.tableView.reloadData() } [/objc] Calling this with the first text parameter will not yet execute the function. Instead it returns a new function with the [String]?, NSError? as parameters. Once that function is called the completionHandler function is finally executed. You can create as many levels of this currying as you want. And you can also leave the last parameter group empty just to get a reference to the fully applied function. Let's look at another example. We have a simple function that sets the text of the resultLabel: [objc] func setResultLabelText(text: String) { resultLabel.text = text } [/objc] And for some reason, we need to call this method asynchronously. We can do that using the Grand Central Dispatch functions: [objc] dispatch_async(dispatch_get_main_queue(), { self.setResultLabelText("Some text") }) [/objc] Since the dispatch_async function only accepts a closure without any parameters, we need to create an inner closure here. If the setResultLabelText was a curried function, we could fully apply it with the parameter and get a reference to a function without parameters: [objc] func setResultLabelText(text: String)() { // now curried resultLabel.text = text } dispatch_async(dispatch_get_main_queue(), setResultLabelText("Some text")) [/objc] But you might not always have control over such functions, for example when you're using third party libraries. In that case you cannot change the original function into a curried function. Or you might not want to change it since you already using it at many other places and you don't want to break anything. In that case we can achieve something similar by creating a function that creates the curried function for us: [objc] // defined in global scope func curry<T>(f: (T) -> (), arg: T)() { f(arg) } [/objc] We can now use it as following: [objc] func setResultLabelText(text: String) { resultLabel.text = text } dispatch_async(dispatch_get_main_queue(), curry(setResultLabelText, "Some text")) [/objc] Probably in this example it might be just as easy to go with the inner closure, but being able to pass around partial applied functions is very powerful and used in many programming languages already. Unfortunately the last example also show a great drawback about the way currying is implemented in Swift: you cannot simply curry normal functions. It would be great to be able to curry any function that takes multiple parameters instead of having to explicitly create curried functions. Another drawback is that you can only curry in the defined order of parameters. That doesn't allow you to do reverse currying (e.g. apply only the last parameter) or even apply just any parameter you want, regardless of its position. Hopefully the Swift language will evolve in this and get more powerful currying features.

Explore related posts