Composing functions on Swift

Understanding function composition looking their types

Franco Consoni
Spark Digital Community
3 min readJan 11, 2019

--

Brief review of the concepts

When we talk about the composition of functions, we are taking two functions in which the output value of one, is the input value of the other. It’s necessary that they’re both waiting for a parameter (which will be the input to the composite function), that is, they are not fully applied. (This process is called partial application)

(f o g)(x) = f(g(x))

When talking about partial application, specifically about a partially applied function: when fewer parameters have been passed than it expected to receive, the function returns a new function that waits for the remaining parameters.

f(x,y,z) : w / f(x,y) -> ( g(z) -> w )

The partial application could not exist without another basic concept, currying. Currying is the process of transforming a function that waits for several parameters, into a succession of functions that take a parameter and return a function that waits for the next parameter. With this in mind, partial application is the process of currying up until a certain parameter, N.

f(x,y,z) : w / f(x) -> ( g(y) -> ( h(z) -> w ) )

(I am not using mathematical notation, although it would be correct to do that, because these concepts belong to mathematics.

All these concepts are better explained here, and how they can be applied in Swift.

In Swift

Let’s declare two simple functions:

func sum(a: Int, b: Int) -> Int {    return a + b}func prod(a: Int, b: Int) -> Int {    return a * b}

And the compose and curry functions:

infix operator << : CompositionPrecedencefunc << <T,U,V>(_ f: @escaping (U) -> V, _ g: @escaping (T) -> U) -> (T) -> V {    return { f(g($0)) }}func curry<A,B,R>(_ f: @escaping (A, B) -> R) -> (A) -> (B) -> (R) {    return { a in { b in f(a,b) } }}

Note: the composing function reads from right to left, like Haskell

Now, let’s curry them:

let add = curry(sum)let mul = curry(prod)

Both functions are of the type:

f :: Int -> Int -> Int

It can be composed when the types fit. An example of this would be:

let success = add(1) << mul(2)success :: Int -> Int

Both functions are partially applied until the last parameter, so they wait for only 1 parameter, and return a value.

Until now, it has been the textbook explanation.

But composition is a very powerful tool, it lets you create complex functions, after starting with simpler ones.

An example would be:

let magic = add << mul(1)

Add” is waiting for both parameters, whilst “mul” is only waiting for one. Why does this work? The answer is in the currying:

add :: Int -> (Int -> Int)

“Add” receives a parameter, and returns a function that waits for the next parameter, so it can be composed with any function that returns “lnt”. In the example we composed it with “mul(1)”.

Let’s see its type:

mul(1) :: Int -> Int

Perfect! The types fit perfectly: “add” waits for a function that returns “Int”, and “mul(1)” is a function from “Int” to “Int(Int -> Int)

Finally, if you understand this idea, you can understand the type of function in the example:

magic :: Int -> Int -> Int

Here, the “mul” function is “delivering” the first parameter to the “add” function, so “add” is now waiting for the second parameter.

But, why doesn’t the type respond to this idea?

It turns out mul(1) is also waiting for a parameter, upon passing it returns a lnt, which ends up being the first parameter of the add function. Therefore, the composite function is waiting for two parameters.

Understanding these concepts allows making things like this:

let advanced = ( ( add << mul(1) ) << add(2) )(1) << mul(1)

I hope that this short article helped you to understanding a little better the world of function’s composition, and hope that can help making more with less.

I invite you to read more about functional programming, and how to use it in your projects. It’s a path without return.

--

--