Functional Swift | Currying

This is the first article in a series on functional programming in Swift. For this one, we'll take a look at 3 different methods for currying in Swift. Each method has it benefits/drawbacks. Ok, let's get into it!

Scenario

Let's assume we have an enum of different fruits:

enum Fruit {
    case apple
    case orange
}

We want to make a function that can create a basket of fruit (Array<Fruit>) from two Fruits.

Method 1 - Functions/Closures

Currying has been made simpler in Swift 5.1, as you can omit the return in single line functions. You can curry by returning functions from your function:

func makeBasket(_ a: Fruit) -> (Fruit) -> [Fruit] {
    { b in
        [a, b]
    }
}
makeBasket(.apple)(.orange) // [Fruit.apple, Fruit.orange]

You can also use closures, which save a bit of typing:

let makeBasket: (Fruit) -> (Fruit) -> [Fruit] = { a in { b in [a, b] } }
makeBasket(.apple)(.orange) // [Fruit.apple, Fruit.orange]

This is probably the best way in most cases. However, if you need generics then closures won't work for you.

Method 2 - Custom Operator

This custom operator will create a curried function from your parameter list:

// Declare the infix operator:
infix operator ~>
// Support for 2 arguments and a return:
func ~> <A, B, C>(_ lhs: (A, B).Type, _ rhs: @escaping (A, B) -> C) -> (A) -> (B) -> C {
    return { (a: A) in { (b: B) in rhs(a, b) } }
}
// Support for 3 arguments and a return:
func ~> <A, B, C, D>(_ lhs: (A, B, D).Type, _ rhs: @escaping (A, B, D) -> C) -> (A) -> (B) -> (D) -> C {
    return { (a: A) in { (b: B) in { (d: D) in rhs(a, b, d) } } }
}
...

It's not necessary to declare this as an operator (a normal function would work just as well), but the operator cleans up the syntax.

It can be fairly inconvenient to setup, but you can add implementations for the operator as you create curried functions with more arguments. It can be used like so:

let makeBasket = (Fruit, Fruit).self ~> { a, b in [a, b] }
makeBasket(.apple)(.orange) // [Fruit.apple, Fruit.orange]

This is shorter than using a closure, but the code behind the scenes to get it working is cumbersome. This method is also unable to work with generics. I hope that if/when we get variadic generics, setting up functions like these will become much easier.

Method 3 - Instance Methods

In Swift, instance methods are curried. What does this mean? Well, if we have a class MyClass, and store one of it's methods to a variable we can call it as a curried function with itself as the first argument:

class MyClass {
    func hello(world: String) -> String {
        "Hello, \(world)!"
    }
}
let greeting = MyClass.hello(world:)
greeting(MyClass())("World") // "Hello, World!"

So by making our function a method of the first argument's type, we can create a curried function:

extension Fruit {
    func makeBasket(_ b: Fruit) -> [Fruit] { [self, b] }
}
let makeBasket = Fruit.makeBasket(_:)
makeBasket(.apple)(.orange) // [Fruit.apple, Fruit.orange]

One benefit of this method is that it supports generics! However, it only supports 2 arguments without combining it with another method.

Those are 3 methods for currying in Swift. In the next article we'll look at enforcing functional principles in our code via linting.