Forget about Elixir for a moment. Think about your current programming language of choice. Let’s imagine you’d written this:
| count = 99 |
| do_something_with(count) |
| print(count) |
You’d expect it to output 99. In fact, you’d be very surprised if it didn’t. At your very core, you believe that 99 will always have the value 99.
Now, you could obviously bind a new value to your variable, but that doesn’t change the fact that the value 99 is still 99.
Imagine programming in a world where you could not rely on that—where some other code, possibly running in parallel with your own, could change the value of 99. In that world, the call to do_something_with might kick off code that runs in the background, passing it the value 99 as an argument. And that could change the contents of the parameter it receives. Suddenly, 99 could be 100.
You’d be (rightly) upset. And, what’s worse, you’d never really be able to guarantee your code produced the correct results.
Still thinking about your current language, consider this:
| array = [ 1, 2, 3 ] |
| do_something_with(array) |
| print(array) |
Again, you’d hope the print call would output [1,2,3]. But in most languages, do_something_with will receive the array as a reference. If it decides to change the second element or delete the contents entirely, the output will not be what you expect. Now it is harder to look at your code and reason about what it does.
Take this a step further—run multiple threads, all with access to the array. Who knows what state the array will be in if they all start changing it?
All this is because most compound data structures in most programming languages are mutable—you can change all or part of their content. And if pieces of your code do this in parallel, you’re in a world of hurt.
By coincidence, Jessica Kerr (@jessitron) tweeted the following on the day I updated this section:
It’s spot-on.