Practical Functional Programming in Swift: The Fundamentals
As Swift developers, we can’t ignore functional programming any longer. With the introduction of SwiftUI and Combine frameworks, Apple has made functional programming a mainstream paradigm in the iOS and macOS engineering realm.
The goal of this article is to lay out the three fundamental concepts of functional programming. Although we’ll be talking about Swift, these concepts transcend any specific language. You can do functional programming with JavaScript, Kotlin, or Java and still find them useful.
What is Functional Programming
If you search Google for a definition of functional programming, you’ll likely find many different and controversial opinions. They boil down to the idea that functional programming is programming with pure functions. Your app is one big pure function.
That definition may work well for academics, because functional programs are a means of building formal mathematical proofs. But what about industrial programmers like us?
In industrial projects, we do have side effects. We render the user interface, make payments, write to files, use the network, and send emails. These are all side effects. We want our users to be able to do all this stuff. Side effects are the reason why our apps are useful.
We write apps, because we want side effects.
What we really want in functional programming is to have as many lines of code as possible in pure functions and as few as possible in side effects.
The essence of functional programming is that it makes the distinction between pure code and impure code and minimizes the latter.
Just like object-oriented programming, functional programming is a paradigm. It has a set of principles that shape your mental framework as you conduct the art of programming. The price of functional programming is that getting started with it is hard. We need to learn many concepts before we can write useful programs.
Core Concepts of Functional Programming
We can look at every functional program as a set of three concepts. These are very important to know and understand. I’ve learned them from Eric Nordman’s podcast and they serve me well.
In a nutshell, a functional program can be broken into three parts: functions, effects and data.
Object-oriented programming has its own concepts – encapsulation, inheritance and polymorphism. You can’t write a program in an object-oriented way without these three.
Swift is multi-paradigm language, which means that we can do both functional and object-oriented programming with it. Which one should we use? The answer is both. The two paradigms are not mutually exclusive. If you pick functional programming, you simply use functional skills more often than the object-oriented ones and vice-versa.
This allows you to build the solution in terms of a problem instead of a limited set of tools. You don’t want to use functional programming for websocket communication layer. Conversely, Fourier transform is naturally described with functions instead of objects and classes.
Therefore, functions, effects, and data are very important to understand when doing both functional and object-oriented programming. Let’s learn about what these are.
Functions
I am sure that you already have a good understanding of what a function means in the object-oriented world.
Remember, in functional programming, we must distinguish between what’s pure and what’s not. We just named the three core concepts: functions, data and effects. By saying a function we really mean a pure function.
What is a pure function?
- A pure function cannot depend on anything that changes. For example: the time changes, the network changes, and the iOS device’s properties and settings change.
- A pure function doesn’t change anything by itself.
- You can call a pure function as many times as you want and it will give you the same result every time.
If all functions involved in the expression are pure functions, then the expression is referentially transparent [2].
Writing functions this way has a profound consequence. It makes it easy to reason about your code and predict its behavior. Pure functions are easy to test and reuse, since they don’t depend on when and where they are run.
Here you can learn more about Swift functions from the FP perspective.
Effects
Calling pure function many times gives the same result each time. Calling an effect many times does not. Imagine launching a rocket. That is an effect. Launching three rockets is different than launching one.
Effects depend on how many times they are called and when they are called.
Reading from a global or a mutable variable is another example of an effect, because it depends on when we do this. We can get different answers at different moments in time.
Effects are hugely important. They are the point where your code meets the real world.
At the same time, effects are the most troublesome part of a program, because it makes a difference when, in what order, and how many times you run them.
Some languages make it difficult to run an effect. For instance, Haskell has a special parameterized type called IO
. Any value in an IO
context must stay in this context. This doesn’t let you mix pure code with impure.
What do we have in Swift to separate pure functions and effects? Nothing. When we write functions in Swift, we have to trust ourselves that certain functions are pure and certain functions have side effects.
Data
Data is information about the outside world, represented in the memory of a computer.
Data is inert, meaning it doesn’t run itself. Data just is what it is [3].
Data requires interpretation. What is a name? Is it the name of a restaurant or is it the name of the article that I read this morning [3]?
Most programming languages, including Swift, solve these problems with types. Types are important because they define the kinds of computation allowed on certain data so that data knows how to interpret itself.
Reading from a mutable data structure is an effect. Therefore, in functional programming we strive to make use of immutable data structures.
An immutable data structure is one that cannot change.
What’s Next
Understanding data, functions and effects is a prerequisite if you are doing functional programming with Swift. But it’s not enough. We need tools and techniques to shape our code according to these principles. These include immutability, higher-order functions, recursion, and functional architecture. These topics will be the subjects for upcoming articles.
I believe that the future of Swift engineering is hybrid. We will pick the best features and ideas from both functional and object-oriented paradigms in order to solve the problem at hand.
Thanks for reading!
If you enjoyed this post, be sure to follow me on Twitter to keep up with the new content. There I write daily on iOS development, programming, and Swift.