Functional Programming in Swift

Thoughts on Functional Programming in Swift

Like most of you, I have to use Objective-C at my day job. I could only craft my Swift skills at night. Swift is not a purely functional language. It can be use imperatively because all frameworks from Apple are written in Objective-C at the time of writing. However, it is also functional, learning from modern languages like Haskell, F#, etc. At the beginning when reading Swift blogs and tutorials, many people talked about terms like functors and monads, those sounded alien-like to me. I started to learn Haskell to understand what they were talking about. I’m not an expert, and still learning functional programming and Swift, but I wanted to share what I’ve learned so far. Hopefully it will save you some troubles to get into the functional programming world.

Key Concepts

Higher-order functions

One key concept of functional programming is higher-order functions. According to Wikipedia, a higher-order function:

  • takes one or more functions as an input
  • outputs a function

In Swift, functions are first-class citizens, like structs and classes, as we can see in the following example:

let addFive = { $0 + 5 }
addFive(7) // Output: 12

We define a function as an inline closure, and then assign it to an inline closure. Swift provides shorthand argument to it. Instead of by name, we can refer to the arguments by number, $0, $1 and so on.

func addThreeAfter(f: Int -> Int) -> Int -> Int {
    return { f($0) + 3 }
}
let addEight = addThreeAfter(addFive)
addEight(7) // Output: 15

The argument type Int -> Int means it is a function that takes an Int as an argument, and returns an Int. If a function requires more than one arguments, for example, it has two Int argument, we can define the type as (Int, Int) -> Int.

The return type of addThreeAfter is also a function. It equivalents to func addThreeAfter(f: Int -> Int) -> (Int -> Int).

[addFive, addEight].map { $0(7) } // Output: [12 15]

map is a special function for container type, such as Array, Optional. It unwraps values from the container type, then applies a transform for each, and wraps them again. We use map here to pass 7 as an argument to each function in the array, and wraps the results into a new array.

In summary, we can assign functions to variables, store them in data structures, pass them as arguments to other functions, and even return them as the values from other functions.

Pure functions

A function is called a pure function if it has no observable side effects. But what the heck is side effect?

A function or expression is said to have a side effect if, in addition to returning a value, it also modifies some state or has an observable interaction with calling functions or the outside world.
— from Wikipedia

The two functions addFive and addEight are pure functions. They don’t modify the input value, or change any global state. In the example below, addFive2 modifies the input, so it is not a pure function.

func addFive2(inout a: Int) -> Int {
    a += 5
    return a
}
var a = 7
addFive2(&a)
a // Output: 12

Functions that access or modify a database, the local file system, or printing strings to the screen are also considered impure. Because they modify the state of the database records, file system, and display buffers respectively. Sometimes side effects are desirable. Without side effects, we could not interact with the program.

Haskell introduces types like IO to separate pure and impure layers. But in Swift, we don’t need to worry too much about this separation. We could still use Cocoa APIs as usual. But, I strongly encourage the use of pure functions whenever possible. With pure functions, the code is more predictable. A pure function will always return the same value if given the same input. It’s easy to test, too. We don’t need to mock a data structure to satisfy its internal state requirements to test a function.

Imperative & Functional Programming

All above are very theoretical. You may want to know how functional programming with help us solve problems in a better, clearer, or less error-prone way. What are the benefits we could gain from it?

First, you need to understand that we could do almost anything in imperative programming, and functional programming does not extend the possibilities of what we could do.

Second, if you come from the imperative programming world, some functional code is harder to understand at first, especially those with custom operators. It is not because functional programming is hard and obscure, but it’s because our mindsets are trained from years of imperative programming practices.

Take a look at this example of imperative code, which we can rewrite as functional code. These two do exactly the same things.

// Imperative
var source = [1, 3, 5, 7, 9]
var result = [Int]()
for i in source {
    let timesTwo = i * 2
    if timesTwo > 5 && timesTwo < 15 {
        result.append(timesTwo)
    }
}
result // Output: [6, 10, 14]
// Functional
let result2 = source.map { $0 * 2 }
                    .filter { $0 > 5 && $0 < 15 }
result2 // Output: [6, 10, 14]

It is arguable which one is clearer. But from this simple example you can see the main difference between imperative and functional programming. In imperative programming, we instruct the computer how to do something:

  1. Iterate through source
  2. Get the result from the element, and multiply by 2
  3. Compare it with 5 and 15
  4. If it is bigger than 5 and less than 15, put it into result

However, in functional programming, we describe what to do:

  1. Transform each element in source to itself multiplied by 2
  2. Only select the ones with value bigger than 5 and less than 15

I’m not going to persuade you functional programming is better. It’s your personal preference. After all, good code is all about writing code that:

  1. Works as intended
  2. Is clear to you and your team

An Example: Reverse Polish Notation Calculator

I like Swift and functional programming, because it enables me to solve a problem in a different perspective. There is usually more than one way to solve a problem. Exploring a better solution helps us grow to become good developers.

Let me show you a functional example. It is a calculator for algebraic expressions of reverse polish notation, or RPN in short. (It is a Swift implementation of the Haskell example in Learn You a Haskell for Great Good.)

A RPN expression of (3 + 5) 2 is 3 5 + 2 . You may think of this as a stack of numbers. We go through the RPN expression from left to right. When encountering a number, we push it onto the stack. When we encounter an operator, we pop two numbers from the stack, use the operator with those two numbers, and then push the result back onto the stack. When reaching the end of the expression, the only one number left on the stack is the result (assuming the RPN expression is valid). For more explanation about RPN, please check on Wikipedia.

We want a function that returns the result for an RPN expression.

func solveRPN(expression: String) -> Double {
    // Process the expression and return the result
}

Given a RPN expression String “3 5 + 2 *”, first we need to transform it into an array of elements that we can process. There are two kind of elements, operand and operator. The Enum data type in Swift comes in handy for defining the element. We name it RPNElement.

enum RPNElement {
    case Operand(Double)
    case Operator(String, (Double, Double) -> Double)
}

Next, we split the expression into an array of Strings, then map it into an RPNElement array.

extension String {
    func toRPNElement() -> RPNElement {
        switch self {
        case "*": return .Operator(self, { $0 * $1 })
        case "+": return .Operator(self, { $0 + $1 })
        case "-": return .Operator(self, { $0 - $1 })
        default: return .Operand(Double(self.toInt()!))
        }
    }
}
func stringToRPNElement(s: String) -> RPNElement {
    return s.toRPNElement()
}
func solveRPN(expression: String) -> Double {
    let elements = expression.componentsSeparatedByString(" ").map(stringToRPNElement)
        // Further process
}

Next, we will go through the array and process it according to how RPN works, as I described earlier. We reduce the array into a Double array. Assuming the expression is valid, the Double array should only contain one element. It will be the result we want.

func solveRPN(expression: String) -> Double {
    let elements = expression.componentsSeparatedByString(" ").map(stringToRPNElement)
    let results = elements.reduce([]) { (acc: [Double], e: RPNElement) -> [Doublein
        switch e {
        case .Operand(let operand):
            return [operand] + acc
        case .Operator(let op, let f):
            let r = f(acc[0], acc[1])
            return [r] + acc[2..<acc.count]
        }
    }
    return results.first ?? 0
}
solveRPN("3 5 + 2 *") // Output: 16

Where to Go From Here?

If you are interested in learning more about functional programming, I highly recommend the following two books:

Even though the second book is written for Haskell, but the concepts also apply to Optional in Swift as well. Besides, it explains Functor, Applicative, Monad in details.

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

Subscribe via RSS