I don't get what exactly means the equal sign in Elixir. What is unclear is that it looks like a mix between assignment and a pattern matching operation.
iex(1)> x=4
4
iex(2)> y=5
5
iex(3)> 3=y
** (MatchError) no match of right hand side value: 5
iex(3)> y=3
3
iex(4)> y=x
4
I understand that in Elixir, the equals operator means to match the left side of the = sign to the the right side. First two lines make sense to me. x and y are unbound variables, hence they could match anything. They are bound as they match. Consequently, I understand the third line. You can't match 3 with 5.
Where I start to loose my head is why the hell the two last lines are executed without giving the same error. It looks like the equal sign is back to being an assignment operator only.
I've try to accept this behaviour as a fact without full understanding and tried to go further in the learning of the language. But as pattern matching is one of the core mechanism of Elixir, I'm constantly lock and feel I should go back to this original question. I will not go any further before I fully understand what exactly happens with the "=" sign and what is the logic.
The equals sign means: "try to fit the value of expression on the right to the shape on the left and assigning values accordingly". So left and right side are different and you cannot switch them. On the right side all variables have to be bound, because it is an expression. On the left side even if you use variables that are already bound they will be reassigned.
So first thing is that on the right you can have any expression your want:
{:error, :enoent} = File.open("foo")
but you can't have an expression on the left side:
iex(1)> File.open("foo") = {:error, :enoent}
** (CompileError) iex:1: cannot invoke remote function File.open/1 inside match
In case of
y=3
5=y # y gets evaluated to 3 and then you pattern match 3=5
and it fails. But you can do
y=3
y=5 # y gets reassigned.
On the left hand side you can only have "shape" which may be arbitrarly nested datastructure:
[a, b, %{"a" => {1, c}}] = [1, 2, %{"a" => {1, 2}]
# c is now assigned value of 2
So pattern matching is used to either destructure data or to assert some condition like
case File.open("foo") do
{:ok, contents} -> enjoy_the_file(contents)
{:error, reason} -> print_error(reason)
end
or if you want to assert that there is only one entity in the database instead of firstly asserting it exists and then that there is only one you can pattern match:
[entity] = Repo.all(query)
If you want to assert that the first value in a list is one, you can pattern match:
[1 | rest] = [1, 2, 3]
There are some gotchas when pattern matching. For example this:
%{} = %{a: "a"}
will match, because shape on the left side is a map and you don't require anything more so any map will match. However this won't match:
%{a: "a"} = %{}
because shape on the left says "give me a map with a key of atom :a
.
If you would like to match on a variable you may write something like this:
a = 1
{a, b} = {2, 3}
but this will assign a
the value 2. Instead you need to use pin operator:
a = 1
{^a, b} = {2, 3} #match fails
I wrote more about pin operator in this answer: What is the "pin" operator for, and are Elixir variables mutable?