In the book 'Elixir in Action', one of the examples has a function that is tripping up my understanding of pattern matching:
def add_entry(
%TodoList{entries: entries, auto_id: auto_id} = todo_list,
entry
) do
entry = Map.put(entry, :id, auto_id)
new_entries = HashDict.put(entries, auto_id, entry)
%TodoList{todo_list |
entries: new_entries,
auto_id: auto_id + 1
}
end
The first parameter, %TodoList{entries: entries, auto_id: auto_id} = todo_list, the book explains "...Furthermore, you keep the entire instance in a todo_list variable"
This confuses me because I thought variables get bound on the left side of a '=' pattern matching operator. Could someone help explain what is happening with the first parameter and how the incoming values are able to be used inside the function body?
I thought variables get bound on the left side of a '=' pattern matching operator
That's correct, in this case the entries and auto_id variables are bound. The right-hand side todo_list is bound from the argument to the function.
It's like doing this:
iex(1)> foobar = %{foo: "foo", bar: "bar"}
%{bar: "bar", foo: "foo"}
iex(2)> %{foo: matched} = foobar
%{bar: "bar", foo: "foo"}
iex(3)> matched
"foo"
The only difference when doing it in a function signature is that the first step of defining what becomes the right-hand-side is handled automatically.
As the book says, you can define a function signature like this:
def do_something_with_foo(%{foo: matched} = original)
Which is explained above, where both matched and original are available in the function body. If you only care about the matched values, you can omit the right-hand side:
def do_something_with_foo(%{foo: matched})
In this case, only the matched value matched will be available. The match still happens, but the data structure passed as the first argument to the function that is used implicitly as the right-hand side, as if you had used =, is not bound to a variable.
With variables:
iex(2)> x = %{a: 1, b: 2}
%{a: 1, b: 2}
iex(3)> %{a: 1, b: 2} = y
** (CompileError) iex:3: undefined function y/0
With function parameter variables:
defmodule A do
def go1(z = %{a: a}) do
IO.inspect z
IO.puts a
end
def go2(%{a: a} = z) do
IO.inspect z
IO.puts a
end
end
In iex:
iex(4)> c "a.ex"
warning: redefining module A (current version defined in memory)
a.ex:1
[A]
iex(5)> map = %{a: 1, b: 2}
%{a: 1, b: 2}
iex(6)> A.go1(map)
%{a: 1, b: 2}
1
:ok
iex(7)> A.go2(map)
%{a: 1, b: 2}
1
:ok
Function args are pattern matched to the function parameter variables. In elixir, unlike in other languages, function parameters can be constants, e.g. 1 or an atom, maps, tuples, etc--not just a variable, like x, y, or z. Here is how go1() works:
A.go1(%{a: 1 b: 2})
|-----+----|
|
| %{a: a} = %{a: 1 b: 2}
V
def go1(z = %{a: a}) do
The "parameter variable" is %{a: a}, and it gets matched to the function argument %{a: 1 b: 2}, which binds a to 1. Then, you might think that you get the pattern match: z = %{a: 1}, however the pattern match %{a: a} = %{a: 1 b: 2} actually returns the right hand side:
iex(10)> %{a: a} = %{a: 1, b: 2}
%{a: 1, b: 2}
Therefore, you get the pattern match: z = %{a: 1, b: 2}. Here is another demonstration of that:
iex(13)> z = %{a: a} = %{a: 1, b: 2}
%{a: 1, b: 2}
iex(14)> a
1
iex(15)> z
%{a: 1, b: 2}
Here is how go2() works:
A.go2(%{a: 1 b: 2})
|-----+----|
|
| z = %{a: 1, b: 2}
V
def go2(%{a: a} = z)
z is the parameter variable and it gets matched to the function argument %{a: 1 b: 2}. The match z = %{a: 1 b: 2} returns the right hand side:
iex(10)> z = %{a: 1, b: 2}
%{a: 1, b: 2}
So, next you get the pattern match: %{a: a} = %{a: 1, b: 2}, which binds a to 1.
Therefore, all is consistent: with every pattern match, the pattern containing the variables is on the left of =, and the values are on the right side. If you are looking for a rule it's: in the parameter list for a function definition, the thing on the right of an = sign is the "parameter variable". The thing on the left is a pattern that will get matched after the "parameter variable" is matched to the function argument (or as you would say in other languages: "after the function argument is assigned to the parameter variable").
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With