Functional Programming in Swift: An Introduction

As a recent convert to the virtues of functional programming I would like to introduce the concept to newcomers in a very simple way, outlining some of the many benefits that can be enjoyed from its application. This first article is more theoretical with only one simple code comparison. More exploration of Functional Programming approaches will be examined in a future article soon!

Functional Programming: A Definition

First and foremost, functional programming is not a language or a syntax¹ , but rather a programming paradigm — a way of solving problems by decomposing complicated processes into simpler ones. As the name implies, the unit of composition for this approach is the function; and the goal of the function is to avoid changing state or mutating values outside of its scope. In the world of Swift, this means using vars as little as possible.

Lucky for us, Swift has recognized the benefits of functional approaches and found ways to accommodate them in the language. The focus of our article today is to explore one example of this approach and examine why it is so beneficial.

[1] I hear you asking “…but aren’t there functional programming languages?” Yes, (Haskell being the most common) but functional programming approaches can be used outside of such exclusive languages.

Imperative and Functional Approaches: A Comparison

To appreciate the features and advantages of a functional approach, let’s compare a basic problem solved in two different ways. First, an imperative approach. Imperative, in this context means the opposite of functional: an approach in which statements are used to change state within the program.

Let’s consider how this is imperative. Notice that we are manipulating the values inside a mutable array called numbers, and then printing to the console. Consider your own answers for these questions which we will discuss soon:

  1. What process are we trying to achieve in our code?

2. What happens if another thread tries to access the numbers array during this process?

3. What happens if we later want to access the original values stored in numbers?

4. How reliably can we test this code?

Have you thought about your answers? Ok. Now let’s consider a functional alternative:

In this snippet we are getting a similar result in the console, but approaching the problem in a very different way. Notice that this time, our numbers array is immutable with the let keyword. We have moved the process of multiplying numbers into a method stored in an extension on Array. While we are still using a for loop and updating a variable called output, notice that the scope of this variable is limited to the method. Similarly, our input argument (in this case numbers) is being passed by value into the method giving it the same scope as our output. The method is called, and we can print both numbers and result to the console.

Let’s consider our three questions again.

  1. What are we trying to achieve in our code?

In our example, we are performing a fairly simple process, multiplying numbers in an array by ten. In the imperative snippet, you had to think like a computer, following the instructions in the for loop to determine the outcome. The code showed you how the result is being achieved. In the functional approach however, the how is wrapped in a method, so provided the method was implemented in another file, you would see only numbers.timesTen(). The code clearly communicating what is being achieved rather than how it is being achieved. This is called declarative programming and it is not hard to see why it is a desirable practice. An imperative approach forces the developer to understand how the code works in order to determine what it does. Functional programming by comparison is far more expressive and allows the developer the luxury to assume that the method does what it says it does! (Obviously this assumption should only be afforded to tested code).

2. What happens if another thread tries to access the numbers array during this process?

Our above examples exist in isolation, however in a complex multi-thread environment, it is absolutely possible for two threads to attempt to access the same resource concurrently. In our imperative approach, it’s not hard to see that when another thread accesses the numbers array for a process, the result of that process will absolutely be dictated by the order in which the access occurs. This is called a race condition and can lead to very unpredictable behaviour, and even instability that can cause crashes.

By comparison, our functional approach has no side effects. In other words, the output of the method has not changed any of the stored values in our system, and the output of the method is determined solely by the input. With this being the case, any other thread that accesses the numbers array, will always receive the same values and its behaviour will be stable and predictable.

3. What happens if we later want to access the original values stored in numbers?

This is a continuation of our ‘no-side effect’ conversation. Obviously, changes in state are not tracked. Unless you explicitly implement a design pattern for this purpose (eg. memento), you cannot role-back to a previously stored value. Therefore in our imperative approach, we lost the original state of our numbers array when we performed our process on it. Our functional solution however, retained the original numbers array and then output a new array with our desired values in result. This effectively leaves the original array accessible for future processes.

4. How reliably can we test this code?

Again, because our functional approach eliminates all side effects, the process that is being tested is entirely contained within the method. The input will never experience change in state, and so you could run a test in a loop as many times as you want and produce the same result every time. That is very easy to test. By comparison, if we tested the imperative solution in a loop, the input value would change and so we would have a different result after each iteration.

Summary of Benefits

As we have seen from this very simple example, functional programming is the bees-knees when it comes to dealing with model data because:

  • It is declarative
  • It eradicates threading issues like race-conditions
  • It leaves state in tact for future use
  • It is easy to test

As usual, thank you for reading. If you are new to functional programming, I hope that you can begin to see the value it will bring to your coding, and get excited about exploring functional techniques in Swift in the second article of this series.

Did you enjoy this article? Perhaps try one of these:

Mastering Generics with Protocols: The Specification Pattern

Polymorphism: Theory and Techniques in Swift

Designing Entity Hierarchies in iOS: Class Inheritance v Composition