问题
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?
回答1:
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.
回答2:
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. And, in elixir 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.go1(%{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").
回答3:
When you specify %{map_thing: stuff} = map_var
you're saying I expect that this var will contain the variable map_thing
, and I want you to put the entire contents of this map into the variable map_var
. You can use this to explicitly specify required keys in that map, and also grab all the optional ones and "bind" them to your map_var
You can do all sorts of useful stuff with this like creating a sort of psuedo guard in case
case some_var do
%MyStruct{} = struct_var ->
# we are saying we expect this var to be of the `MyStruct` variety.
other_case ->
do_something_else()
end
来源:https://stackoverflow.com/questions/56763498/understanding-pattern-matching-in-elixir-function-parameters