We call a function pure if it meets the following criteria
- It does not depend on hidden state, or equivalently it only depends on its inputs.
- Evaluation of the function does not cause side effects
In short the internal work of a pure function is isolated from the rest of the program.
This is made clear by two examples:
# A pure function def min(x, y): if x < y: return x else: return y # An impure function exponent = 2 def powers(L): for i in range(len(L)): L[i] = L[i]**exponent return L
The function min
is pure. It always produces the same result given the
same inputs and it doesn't affect any external variable.
The function powers
is impure for two reasons. First, it depends on a
global variable, exponent
, which can change [*]. Second, it changes the
input L
which may have external state. Consider the following execution:
>>> data = [1, 2, 3] >>> result = powers(data) >>> print(result) [1, 4, 9] >>> print(data) [1, 4, 9]
We see that powers
affected the variable data
. Users of our function
might be surprised by this. Usually we expect our inputs to be unchanged.
Another problem occurs when we run this code in a different context:
>>> data = [1, 2, 3] >>> result = powers(data) >>> print(result) [1, 8, 27]
When we give powers
the same inputs we receive different outputs; how could
this be? Someone must have changed the value of exponent
to be 3
,
producing cubes rather than squares. At first this flexibility may seem like a
feature and indeed in many cases it may be. The cost for this flexibility is
that we need to keep track of the exponent
variable separately whenever we
use powers
. As we use more functions these extra variables become a
burden.
[*] | A function depending on a global value can be pure if the value never changes, i.e. is immutable. |
Impure functions are often more efficient but also require that the programmer "keep track" of the state of several variables. Keeping track of this state becomes increasingly difficult as programs grow in size. By eschewing state programmers are able to conceptually scale out to solve much larger problems. The loss of performance is often negligible compared to the freedom to trust that your functions work as expected on your inputs.
Maintaining state provides efficiency at the cost of surprises. Pure functions produce no surprises and so lighten the mental load of the programmer.
As an added bonus, testing pure functions is substantially simpler than testing impure ones. A programmer who has tried to test functions that include randomness will know this first-hand.