What is the "pin" operator for, and are Elixir variables mutable?
Data in elixir is immutable, variables though are re-assignable. What can make elixir slightly confusing is the combined assignment and pattern matching that you are seeing.
When you use the equals sign with a variable reference on the left elixir will first pattern match the structure, and then perform an assignment. When you have just a sole variable reference on the left, it will match any structure and so will be assigned like so:
a = 1 # 'a' now equals 1
a = [1,2,3,4] # 'a' now equals [1,2,3,4]
a = %{:what => "ever"} # 'a' now equals %{:what => "ever"}
When you have a more complex structure on the left elixir will first pattern match the structures, then perform the assignment.
[1, a, 3] = [1,2,3]
# 'a' now equals 2 because the structures match
[1, a] = [1,2,3]
# **(MatchError)** because the structures are incongruent.
# 'a' still equals it's previous value
If you want to value match against the contents of a variable you can use the pin '^':
a = [1,2] # 'a' now equals [1,2]
%{:key => ^a} = %{:key => [1,2]} # pattern match successful, a still equals [1,2]
%{:key => ^a} = %{:key => [3,4]} # **(MatchError)**
This contrived example could also have been written with 'a' on the right hand side and without the pin:
%{:key => [1,2]} = %{:key => a}
Now say you wanted to assign a variable to part of a structure but only if part of that structure matched something stored in 'a', in elixir this is trivial:
a = %{:from => "greg"}
[message, ^a] = ["Hello", %{:from => "greg"}] # 'message' equals "Hello"
[message, ^a] = ["Hello", %{:from => "notgreg"}] # **(MatchError)**
In these simple examples the use of the pin and pattern matching isn't immediately super valuable, but as you learn more elixir and start pattern matching more and more it becomes part of the expressiveness that elixir affords.
Here is my minimalistic approach:
The equal symbol (=) is not just assignation, two things happen here:
- pattern matching.
- if the pattern matches, then this leads to an assignment from right to left. Otherwise, an error is reported.
Think of "=" as in algebra, this indicates the left and the right side of the equation are representing the same, so if you have x = 1, the only value for x is 1.
iex(1)> x = 1 # 'x' matches 1
1
iex(2)> x # inspecting the value of 'x' we get 1, like in other languages
1
iex(3)> x = 2 # 'x' matches 2
2
iex(4)> x # now 'x' is 2
2
so how can we use 'x' to compare and not to assign it a new value?
We need to use the pin operator ^:
iex(5)> ^x = 3
** (MatchError) no match of right hand side value: 3
we can see that 'x' value is still 2.
iex(5)> x
2
The data in Elixir is still immutable, but there are couple of shorthands, that let you type less or don't worry about finding new names. In Erlang, you could often see code like this:
SortedList = sort(List),
FilteredList = filter(SortedList),
List3 = do_something_with(FilteredList),
List4 = another_thing_with(List3)
In Elixir, you could just write:
list = sort(list)
list = filter(list)
list = do_something_with(list)
list = another_thing_with(list)
This is exactly the same, but it looks a little better. Of course the best solutions would be to write like this:
list |> sort |> filter |> do_something |> another_thing_with
Every time, you assign new thing to list
variable, you get new instance:
iex(1)> a = 1
1
iex(2)> b = [a, 2]
[1, 2]
iex(3)> a = 2
2
iex(4)> b
[1, 2] # first a did not change, it is immutable, currently a just points to something else
You just say, that you are no longer interested in the old a
and let it point to something else. If you are coming from Erlang background, you probably know the f
function from shell.
A = 1.
f(A).
A = 2.
In Elixir you just don't have to write the f
. It is done automatically for you. This means, that every time, you have variable on the left side of the pattern match, you are assigning new value to it.
Without the ^
operator, you wouldn't be able to have a variable on the left side of pattern match, because it would get new value from the right side. ^
means do not assign new things to this variable - treat it as a literal value.
That is why in Elixir
x = 1
[1, x, 3] = [1, 2, 3]
is equivalent in Erlang to:
X = 1,
[1, CompletelyNewVariableName, 3] = [1, 2, 3]
and:
x = 1
[1, ^x, 3] = [1, 2, 3]
is equivalent to:
x = 1
[1, 1, 3] = [1, 2, 3]
which in Erlang is:
X = 1,
[1, X, 3] = [1, 2, 3]