No matter how hard we try, some impurities will always exist. We can abstract them away, ignore them or make sure that they won’t affect normal program usage, but we can’t ever get rid of them.

The two main sources of such impurities are:

  • Hardware - because all programs run on actual devices.
  • Time - because everything happens is trapped in the one-directional flow of time.

Hardware caused impurities are caused by physical constraints of the devices on which we run our programs. Some such devices are:

  • CPU
  • Memory
  • Power supply or battery
  • etc.

Let’s illustrate this on another example - we’ll write a function that receives a non-negative number and returns an array of all the numbers from 0 to that number, but not the number itself. If it receives a negative number, the function should just return an empty array.

function range(n){
   let array = [];
   for(let i = 0; i < n; i++){
      array.push(i);
   }
   return array;
}

range(-5);
//=> []

range(0);
//=> []

range(1);
//=> [0]

range(6);
//=> [0, 1, 2, 3, 4, 5]

So far, so good. But what if we give it a huge number?

range(1000000000);

In Google Chrome console instead of the result I get this error message:

Google Chrome Memory Error

When I try the same thing in a NodeJS shell (another handy JavaScript console), I get a somewhat different error message, that basically says the same thing - we ran out of memory!

FATAL ERROR: invalid array length Allocation failed - JavaScript heap out of memory
 1: 0x9fd5f0 node::Abort() [node]
 2: 0x94a45d node::FatalError(char const*, char const*) [node]
 3: 0xb7099e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
 4: 0xb70d17 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
 5: 0xd1a905  [node]
 6: 0xcf3445  [node]
 7: 0xe7b96e  [node]
 8: 0xe7baba  [node]
 9: 0x10185b3 v8::internal::Runtime_GrowArrayElements(int, unsigned long*, v8::internal::Isolate*) [node]
10: 0x13cc8f9  [node]
Aborted (core dumped)

Obviously, things will be different on a different machine. That itself gives us a hint that there is an impurity. The function evaluation depends on something other than its parameters, in this case the size of the available memory.

Interestingly, this example also demonstrates side-effects. Whenever one process reserves a piece of memory, the same memory cannot be used for other programs at the same time. So, by being memory intensive, our function may actually prevent other programs from running properly or at all. Most of the time operating systems are pretty good at policing the resource allocation, but this still remains a possibility.

On the off chance that you have more memory available in your environment and the example actually runs fine for you, just add a few zeroes and try again. If it still works, keep adding those zeroes and you’ll run into the limit eventually. You have to, it’s not infinite.

Another obvious hardware dependency is power supply. A computer cannot work without electricity. If someone pulls the plug or the battery runs out during a function evaluation, it will be interrupted. This is also an example of a side-effect as functions with a higher strain on resources will drain the battery faster.

Let’s see another example with side effects. We’ll write a function that counts to n and then returns the count.

function count(n){
   let sum = 0;
   for(let i = 0; i < n; i++){
      sum += 1;
   }
   return sum;
}

Yes, I could have just said return n, but I needed an example that would intentionally put a strain on the CPU. Again, let’s try it out:

count(0);
//=> 0

count(4);
//=> 4

count(10);
//=> 10

So far, so good. It’s a pure function, supposedly doesn’t have side effects and unlike the previous one it doesn’t really need much memory - there are just three variables involved: n, i and sum.

Then what would happen if we were to call it with a very large number?

count(1000000000000);
//=> 1000000000000

It actually did get the result, but when I ran it on my laptop it did something else, too. The CPU temperature jumped for a moment and the fan speed increased. That, in turn, slightly increased the temperature of my room.

If your code warms up the room in which its computer is, it is safe to claim that it produces a side effect.

Time based impurities are caused by basic laws of nature. Things simply take time to happen and nothing less than time-travel can change that.

Let’s try running count with an even larger parameter. Be warned, you may have to kill the JavaScript console after the next example.

count(1000000000000000000);

//So, where is the result? 
//still waiting...

//after some time...
//still waiting...

//a minute goes by...
//yep, still waiting...

//in the mean time, you remember that lame old knock-knock Java joke

//but, we're still waiting...

While you could actually wait for the result, I recommend killing the console. It’s not like we do not know the result and the point was already made - the passage of time while the function is being evaluated is a side effect in its own right.

How did this side effect manifest? Well, we can count at least two ways:

  • We got bored while we waited for it to finish.
  • We stopped the function from further evaluation. It is a user action, but one that has been directly caused by the long evaluation time.

So, these are the necessary evils we have to live with. Functional programming allows us to write clean and elegant code, but it is by no means magic. Fundamentally, we still have to conform with the same physical constraints as if we were doing imperative programming.