In this post, I would like to zoom in on pure functions and impure functions in Swift. These concepts may sound exotic, especially if you are unfamiliar with functional programming. I promise you that these concepts aren't as daunting as they seem.
What Is a Pure Function?
Pure functions are a key concept in functional programming. The idea is surprisingly simple. A pure function is a function that has no side effects. To understand this definition, you need to know what is meant by side effects. We explore side effects in a moment. We can rephrase the definition of a pure function by stating that a pure function only depends on or operates on what it is given. Let me illustrate this with an example.
We define a variable with name a
and value 1
. The increment(by:)
function accepts an argument of type Int
and increments the value of a
. We print the initial value of a
, invoke increment(by:)
once, and print the new value of a
.
var a = 1
func increment(by value: Int) {
a += value
}
print(a)
increment(by: 2)
print(a)
The output isn't surprising. The initial value of a
is 1
and the new value of a
is 3
.
1
3
The increment(by:)
function is an impure function because it has side effects. What is a side effect? Every time the increment(by:)
function is invoked, it modifies the value of a
. It modifies state outside its local environment.
Remember the definition of a pure function. A pure function only depends on or operates on what it is given. That is not true for the increment(by:)
function because it depends on the value of a
.
Let's revise the example and convert increment(by:)
from an impure function to a pure function. We can accomplish this by passing the value of a
to increment(by:)
and returning the result of the addition from the function.
var a = 1
func increment(_ initialValue: Int, by value: Int) -> Int {
initialValue + value
}
print(a)
a = increment(a, by: 2)
print(a)
Even though the output in the console hasn't changed, we improved the implementation in several respects. The most obvious improvement is that the increment(_:by:)
function is reusable because it no longer depends on the value of a
. Another significant benefit is that increment(_:by:)
no longer has side effects. It operates exclusively on what it is given, the parameters it defines. This has another important benefit. The testability of increment(_:by:)
has improved significantly. For a given input, the output is always the same. This is a key feature of pure functions. A pure function always returns the same result for a given set of arguments. In other words, a pure function is predictable and that is a good thing.
Because a pure function doesn't depend on anything but the arguments it is given, a pure function has two interesting characteristics. It always accepts one or more arguments and it always returns a value. If you come across a function that doesn't take any arguments or returns nothing, then you can be sure it is an impure function (or a function that does nothing).
A pure function is predictable and that means it is easy to understand what it does. You don't need to take other variables into account to understand the effect of a pure function. This is a powerful concept and a cornerstone of functional programming.
Are Side Effects Bad?
You may think that pure functions are preferred over impure functions. This isn't accurate. Side effects are necessary if your goal is building software people can use. How would you update the user interface of your application if you could only use pure functions, that is, functions that don't have side effects?
It is more accurate to say that you should only use an impure function if you can't accomplish the same result with a pure function.