Elixir: When match? is always true
This is a quick post to highlight what we found is an easy mistake to make when encountering the match?
macro. Which from the documentation is:
“A convenience macro that checks if the right side (an expression) matches the left side (a pattern).”
The important piece of information in that statement is “the left hand side (a pattern)”.
How does match?
work?
Let’s look at match?
with some number literals:
iex> match?(1, 1)
true
iex> match?(1, 2)
false
So far so good, how about match?
with a pattern in it:
iex> match?(%{key: "value"}, %{key: "value"})
true
iex> match?(%{key: "other"}, %{key: "value"})
false
What if we wanted to introduce some variables to make the code more readable. Let’s introduce a contrived right_map
variable.
iex> right_map = %{key: "value"}
%{key: "value"}
iex> match?(%{key: "value"}, right_map)
true
iex> match?(%{key: "other"}, right_map)
false
Everything still looks in order, now how about a left_map_value
and a left_map_other
variable.
iex> right_map = %{key: "value"}
%{key: "value"}
iex> left_map_value = %{key: "value"}
%{key: "value"}
iex> left_map_other = %{key: "other"}
%{key: "other"}
iex> match?(left_map_value, right_map)
true
iex> match?(left_map_other, right_map)
true
Notice that both calls to match?
now return true
, that is not what we want at all. The implementation of the match?
macro sheds some light on what is going on:
defmacro match?(pattern, expr) do
quote do
case unquote(expr) do
unquote(pattern) ->
true
_ ->
false
end
end
end
I won’t go into macros in this post, but if we use the example above, this is equivalent to the expression:
case right_map do
left_map_other ->
true
_ ->
false
end
Remember that variables can be rebound, which is exactly what is happening here. Essentially left_map_other
is getting bound to the value of right_map
, and then true
is returned. It makes no difference what the value of left_map_other
is, the expression above will always be true
. We can prove this by returning the value of left_map_other
:
iex> case right_map do
...> left_map_other -> left_map_other
...> _ -> true
...> end
%{key: "value"}
iex> left_map_other
%{key: "other"}
In the scope of the first case clause left_map_other
is bound to the value %{key: "value"}
, outside of that it is bound to the original value %{key: "other"}
. This is simple enough to fix now that we now what is going on, we just need to pin the left_map_other
so that the value of left_map_other
is the pattern.
iex> match?(^left_map_value, right_map)
true
iex> match?(^left_map_other, right_map)
false
When is match?
useful?
I have found match?
handy when you care about only whether something matches, not binding variables, as we have seen variables bound inside match?
will not be available to the caller. The most common cases include working with Enumerables and during testing. The great thing about match?
is that, as we have seen, the left hand side can be anything that is valid in a case clause. This means that guard clauses are valid, the documentation has an example of this (i've slightly modified it):
iex> list = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]
iex> Enum.filter(list, &match?({:a, x} when x < 2, &1))
[a: 1]
Wrapping Up
So match?
actually does exactly what it says on the tin. It is just important to remember what is really going on when refactoring and introducing variables. It is a macro that I find comes in quite handy.