If you come from object oriented programming, one of the weird things about Elixir and other functional programming languages is for sure immutability.
In many of those we are allowed to assign a value to a variable and then change it during the execution of a program. Replacing a value in a specific memory position with another value is legit, and the readability of our program is certainly better than the following immutable-like example:
# Without reassignment name = "john" name_after_one_function = function1(name) name_after_two_functions = function2(name_after_one_function) name_after_three_functions = function3(name_after_two_functions)
But let’s say we wrote a program that frequently mutates variable values and is also multi-threaded, what happens when a value in memory change while it’s being used by multiple instances of our program? And what if our program is written in a dynamic language that allows variables to change its types?
You can guess the answers, in a concurrent environment we are likely to introduce bugs that are difficult to track and replicate. This leads to defensive programming that makes our code overcomplicated, written ad-hoc to deal with synchronisation issues, thus taking care of the time variable. We will reduce the risks of concurrent processes accessing the same resources, but at a very high price.
Other programming languages instead, like Erlang and obviously Elixir that is built on top of it, embraces immutability.
They simply don’t allow values in a certain memory location to change. Never. Until the variable gets garbage collected or is out of scope.
This way when we reference the value
1 with the variable
a we know for sure that that value won’t change during the execution of your program, and we simply don’t have to take care of synchronisation issues in a concurrent environment.
That’s also why we use the statement “reference the value with”. Look at the following example:
# Simple assignment a = 1 b = 3
= is the match operator and the snippet above is a match, or binding. We are referencing the values
3 with the variables
b. The meaning of the
= operator has to be intended like its algebraic meaning, not like a real assignment.
If you read the first example in this post you might think immutability leads to the multiple assigment scenario explained above. Well that’s only partially true, because Elixir allows variable rebinding:
# rebinding a variable a = 1 a = 3
This might look confusing but no, we didn’t reassign a value here.
Rebinding doesn’t change the state of an object at all, the value is still in the same memory location, but it’s label (variable) now points to another memory location, so immutability is preserved. Rebinding is not available in Erlang, but it is in Elixir. The reasons behind this change are well explained by the Elixir author Josè Valim in this gist.
It might be important to note that there are two cases in which Elixir doesn’t allow rebinding:
In the first case, displayed in the following example, match will fail, it’s not possible to match in the same instruction the variable
# Failed rebinding in pattern matching [a, b, a] = [1, 2, 3] # ** (MatchError) no match of right hand side value: [1, 2, 3]
In the second case, the pin
^ operator, means that we want to treat the variable as a literal value, disallowing a rebinding. You can see an example in the following code:
# Pin operator a = 1 a = 3^ a = 5 # ** (MatchError) no match of right hand side value: 5
I hope this clarify for you the difference between mutability, immutability and the meaning of rebinding in Elixir.