Now that we’ve mastered the concept of pure functions, we’re going to deconstruct it and show how function purity is really just an illusion. However, one that does provide a very useful abstraction that helps us write cleaner and more manageable code.
Let’s use a very simple pure function.
function add(a, b){
return a + b;
}
As we’ve seen in the previous chapter, this JavaScript function is pure - its behavior depends only on its
parameters and there are no side effects. Its only line of code returns the value of an operator expression
a + b
that also happens to be pure. Or does it?
In order to be executed on a computer, any piece of code must be either compiled to a machine-readable set of instructions or interpreted by another program that’s already machine-readable. So, if we zoom in through a couple of layers we’ll eventually get to see what the computer actually gets to run - something like these CPU instructions:
LOAD 12
ADD 14
STORE 17
This example could read as:
- Load the value from a memory location 12 (e.g. parameter a) into CPU register.
- Load the value from location 14 (or b) and add it to the value in the register.
- Store the value from register into a memory location 17.
And that’s it. All the way down our purest and cleanest function actually works like a typical imperative program:
- It depends on a shared global state - the contents of your RAM at the time.
- It modifies the shared global state - the result is written back to the memory.
Note that on this level of abstraction there are no such things as parameters or return values. CPU just does stuff with data it gets from RAM and sends back any results.
All the way down all functions are just imperative procedures.
However, it would not be particularly constructive to just say: Hey none of this stuff is actually pure, let’s just get back to good old imperative programming, it’s the same thing anyway.
Well, that would be a bit trollish and also a tad incorrect. On one hand, the fact is that even pure functions are imperative procedures on the lowest level of abstraction. On the other hand, we really do have all the benefits we sought when we analyzed declarative programming.
For example, let’s consider this function:
function arraySum(array){
let result = 0;
for(let i = 0; i < array.length; i++){
result += array[i];
}
return result;
}
For the sake of simplicity let’s assume that parameter array
is always an array of numbers. We can see that the
function code is clearly imperative - local variables result
and i
are being mutated and code flow is determined
by a for
loop.
Yet, is this function pure?
arraySum([1, 2, 3]);
//=> 6
//and again
arraySum([1, 2, 3]);
//=> 6
Try it out as many times as you want, it will always be a six. The outcome depends only on the input parameters and as we can see that there aren’t any side effects. So, it is pure - even though it relies on plain old imperative code.
What we can learn from this is that what we call pure functions are in fact nothing other than exceptionally well-behaved imperative procedures that have the decency to take all its inputs in one clearly marked designated place and to return all its results in just as orderly manner. They have a clear boundary between what they do not mess with, but are also very protective about their own inner workings and will never be indiscreet enough to reveal their internal state and allow other programs to mess with their own variables.
Now notice that we can’t really tell any of that just from looking at the function call. The imperative stuff is conveniently abstracted away and if we only observe the function as a black box, blissfully oblivious of its inner workings, for all intents and purposes we can say it’s as pure as they get.
Still, it is useful to just be aware of all this. Function purity is an abstract concept. It only exists in the world of ideas along with a point that has no dimensions and other mathematical abstractions. Programs run on actual computers, which are physical devices and as such they are subjected to physical constraints.
Hold onto that thought, we’ll expand on it in the next chapter, where we explore some typical inherent impurities we can never really get away from.