πŸ”— Function Composition in Swift

It’s common to perform an operation on a value and use the resulting value of that operation as the argument for a subsequent operation, and so on.

// Definitions

func square(_ x: Int) -> Int {
    return x * x
}

func increment(_ x: Int) -> Int {
    return x + 1
}

func half(_ x: Int) -> Int {
    return x / 2
}

func describe(_ val: CustomStringConvertible) -> String {
    return "The resulting value is: \(val)"
}

// Usage

describe(increment(square(half(6)))) // "The resulting value is: 10"

This works fine but the code quickly starts looking like Racket, and becomes hard to unpack. Another way to do this same thing is by leveraging function composition, which entails chaining many functions together into one. This idea of concatenating functions can be accomplished in Swift by taking advantage of generics and the infix operator.

// Custom Operator Definition

precedencegroup Group { associativity: left }
infix operator >>>: Group
func >>> <A, B, C>(_ lhs: @escaping (A) -> B,
                   _ rhs: @escaping (B) -> C) -> (A) -> C {
    return { rhs(lhs($0)) }
}

Here we have a function that takes 2 parameters and returns a closure.

  • The first parameter in >>> is a function that takes 1 parameter of type A and returns an instance of type B.
  • The second parameter in >>> is a function that takes 1 parameter of type B (this is where the concatenation occurs) and returns an instance of type C.
  • Lastly, >>> returns yet another function which takes 1 parameter of type A and returns an instance of type C. Type B, being both the output for the 1st parameter and input for the 2nd one, serves as the knot between A and C.
  • The body of the function runs the result of lhs on rhs and returns. These functions are nested internally so that the function parameters, lhs and rhs, can be described as a sequence externally.

We could then do the following:

// Usage With Custom Operator

(half >>> square >>> increment >>> describe)(6) // "The resulting value is: 10"

This then becomes a better and more legible approach of describing a sequence of operations, where functions are chained instead of being nested.