Functional Programming in Swift

An introduction to functional programming and how it can be useful in Swift.

Pol Piella
Pion
7 min readJan 12, 2021

--

What is Functional Programming?

Introduction

Functional programming is a declarative programming paradigm where programs are built by composing and applying small, single-purpose functions.

It is based on the key aspects of immutability, by preferring value types to reference types, treating functions like any other data type, and having pure, modular and fully replaceable methods. This means that you might see an unusual use of functions and heavy reliance on structs and lets in detriment of other mutable/reference types such as class and var.

In this article, I will give an overview of some of the key concepts of functional programming such as immutability, higher-order and pure functions and will have a look at how all of this can fit very nicely with the Combine framework.

Imperative vs Declarative: What is the difference?

First, before we dive into any examples or concepts, we need to understand what a declarative paradigm is and how it differs from the more conventional imperative paradigms.

Imperative programming focuses on how the control flow is organised and executed and which steps it should follow to complete an action, whereas declarative programming focuses on what the logic and goal of your action are without describing the control flow at all. In order to understand this a little bit better, let’s look at an example:

Let’s say that you need to get from your home to work in the morning. What would the declarative flow look like? And the imperative?

The imperative approach would describe how to get to work and the steps required to do so. For example, it would be: get in the car, drive on the motorway, park the car, etc. The declarative approach would simply say: get to work, it will not care how you do so. In short, declarative paradigms only describe the what, whereas imperative paradigms focus on describing the how.

Declarative paradigms do most of the time have an imperative implementation under the hood and neither approach is always correct. The choice must be carefully made and the environment and conditions must be evaluated before picking one.

For instance, taking the example above, there will still be an imperative layer to perform all the logic steps and describe the flow but our API won’t care about them and will just expose the top-level action. The code below shows an example of a very simple implementation to remove all odd numbers from an array both declaratively and imperatively:

Declarative vs Imperative example using Swift

Both examples above achieve the exact same result but we can see that one of them is a lot more explicit and focuses what we are trying to achieve (filter) whereas the other one describes the exact flow on how to achieve the desired result.

Higher-Order Functions (HOFs)

In functional programming, functions are a first-class type. This means that they can be treated as any other data type and can be passed to or even be returned by other functions. This, combined with trailing closures, makes functional programming syntax very easy to read and very simple to use in Swift. Functions that take other functions as an argument or return them are called higher-order functions (HOFs).

Let’s go back to the example above, where we used the filter function to remove all of the odd numbers from our array in a single line of code and with no explicit iteration blocks. In other words, we provided a function, which acted as a transform, to another function, making filter a higher-order function. This is a very common pattern in functional programming and is found widely within the Swift API, with methods such as map, reduce, split, flatmap and many more!

Immutability and side-effects

Immutability describes the inability of change or the state of not changing of a particular object. This is very important in functional programming because it means that any shared-state tends to be avoided wherever possible and data and new values will be computed using small, single-purpose pure functions (we’ll get into this later on).

Now, why is immutability preferred? The truth is that the same results can be achieved using both mutable and immutable types but choosing to design or use immutable data sources/types can have a number of advantages:

  • Code is easier to understand in isolation as variables can’t mutate.
  • Thread safety is a benefit of immutability as using immutable types means that race conditions are prevented.
  • No shared state or side effects.
  • Forces the developer to think more carefully about types and implementations as variables can not be mutated.

In Swift, we can have mutable (var) and immutable (let) variables as well as reference (e.g. class) and value (e.g. struct) types. I won’t go into detail about what these are, but a good article on value vs reference types can be found here and, as you will have probably guessed already, functional programming relies heavily upon value types and immutable variables.

To understand why immutability is key in a functional paradigm, let’s consider the quote below from “Go To Statement Considered Harmful” by Edsger Dijkstra:

[…] Our intellectual powers are rather geared to master static relations and that our powers to visualize processes evolving in time are relatively poorly developed.

This quote highlights the fact that we react quicker and better to code flows that are structured and we are more likely to understand a piece of code if there are fewer jumps, side-effects and fewer dependencies and mutability to worry about.

Pure Functions and Single Responsibility

Another important pillar of functional programming is the separation of concerns and having small building blocks in the shape of functions that we can composite and combine together to build bigger code entities that are entirely modular. A very big advantage of this is that each module or block is easily replaceable and the logic can be tweaked and modified without major consequences.

What are pure functions and how can they help us build a healthier and more scalable codebase?

Pure functions are methods that, when provided with the same input, provide the same exact result regardless of the surrounding environment. In other words, all the parameters and variables do not outlive the scope of the method and they are not affected by any other external factors or side-effects. Let’s look at an example below:

Pure Functions

As we can see above, the notAPureFunction method relies on an external variable which, in turn, is mutable. This means that there is no guarantee that, even though we provide it with the same parameter value twice, that the function returns the exact same value every time. How do we “purify” this method? If instead of depending on an outside variable and having side effects, we handle this logic elsewhere and we pass the boost property as a parameter, we have a pure function that provides the same result every single time provided we pass it the same parameters. This will immediately make our implementation easier to grasp and reduce the risks of other dependencies changing and hence breaking the intended functionality of the method.

Looking at the future: Combine

Now that we have covered the basics of functional programming, we can have a look at the powers of functional programming when combined with other programming frameworks such as Reactive APIs and how this might help to shape the future of the iOS development world.

Let’s look at Combine , an API introduced by Apple at WWDC 2019 that consists of a declarative framework which allows us to process values over time. I will not go into detail about how Combine works but if you are familiar with other declarative frameworks such as RxSwift, it is a Functional Reactive Programming framework that, using Publishers, Operators and Subscribers, we can modify data over time functionally and also react to it by, for example, updating our UI after a network request.

Combine, together with SwiftUI or even UIKit is set to change the way that most of the conventional iOS APIs are designed and already has added support to a lot of the already existing Apple frameworks such as URLSession , NotificationCenter , etc. In the example below, we are going to explore how to use Combine to make a simple URL request to fetch a user, modify the data functionally to adapt to our needs and then update a label when the data is ready.

Functional Reactive Programming using Combine

In the example above, the functionality is the same for both cases but the structure and flow is completely different and for the Combine example, we can see how the use of small functional blocks such as decode , map and assign can help us shape the incoming data to meet our requirements.

As we can see, even though it might seem complicated and overwhelming at first, functional programming becomes very useful when using it together with other APIs such as Combine, where data is treated as a series of events that are produced and can be observed, reacted to and modified over time by modular functional blocks that will transform data.

Conclusion: Handle with caution!

Functional programming and declarative APIs are very useful, make our code a lot simpler to understand and a lot more scalable and backwards compatible. Despite this fact, Swift is not entirely a declarative programming language and consists of multiple programming paradigms but starting to think and write functionally can have a lot of advantages and can be very powerful when combined with other frameworks such as Combine.

It is also very true that the learning curve can be somewhat steep and that there are some drawbacks to declarative frameworks and their use should be very much geared towards the project’s needs and limitations. As always, the best thing is to have a play with some of these concepts and judge by yourself! Here are some links that can help you get started:

Happy Coding!

--

--

Pol Piella
Pion
Writer for

iOS Developer @ Student Beans. Previously Music Tribe