Pattern Matching
Pattern matching is extremely powerful in Elixir and you’ll use it all the time. Let’s see some examples:
Match against Basic Types
# You can match against many data structures, like a map.
# This is a partial match, meaning the 'height' value gets ignored.
%{age: age, name: name} = %{age: 32, name: "Peter", height: 190.47}
IO.inspect([age, name], label: 1)
# Or a Tuple. You can't match partially though.
{a, _, c} = {1, 2, 3}
IO.inspect([a, c], label: 2)
# Or a Keyword list, but you can't match partially either.
[a: a, b: _b, c: c] = [a: 1, b: 2, c: 3]
IO.inspect([a, c], label: 3)
# Or a list, but again, not partially.
[a, b, _] = [1, 2, 3]
IO.inspect([a, b], label: 4)
# But you can match against the head (first element) and tail (the rest) of a list.
[head | tail] = [1, 2, 3]
IO.inspect(head, label: "5 - head")
IO.inspect(tail, label: "5 - tail")
1: [32, "Peter"]
2: [1, 3]
3: [1, 3]
4: [1, 2]
5 - head: 1
5 - tail: [2, 3]
Caveat: Matching against empty Maps
Watch out if you want to match against an empty map, because Elixir will match any map with an empty map, even if the map isn’t empty.
map_empty? = fn
%{} -> true
_ -> false
end
map_empty?.(%{}) # => true
map_empty?.(%{a: 1, b: 2}) # => true
# You can either use the 'map_size/1' guard
map_empty_guard? = fn
map when map_size(map) == 0 -> true
_ -> false
end
map_empty_guard?.(%{}) # => true
map_empty_guard?.(%{a: 1, b: 2}) # => false
# Or a comparison to match against an empty map
map_empty_comparison? = fn
map when map == %{} -> true
_ -> false
end
map_empty_comparison?.(%{}) # => true
map_empty_comparison?.(%{a: 1, b: 2}) # => false
Match in Function Clauses
# You can match (partially) in function clauses:
fun = fn
%{status: status} -> status
%{age: age} -> age
end
fun.(%{status: :active, height: 190.47}) # => :active
fun.(%{age: 32, height: 190.47}) # => 32
# Or in Module functions
defmodule RunElixir.Profile do
def details(%{status: status}), do: status
def details(%{age: age}), do: age
end
RunElixir.Profile.details(%{status: :inactive, height: 190.47}) # => :inactive
RunElixir.Profile.details(%{age: 30, height: 190.47}) # => 30
Match against Strings
# You can match against the end of a string
"My name is " <> name = "My name is Batman"
IO.inspect(name, label: 1)
# Or you can match against specific bytes
#
# This is extremely useful for deconstructing a string into fixed-size byte chunks.
# For example, to implement a communication protocol for which you know that
# the first 3 bytes are one argument, and the next 8 bytes are the second, and so on.
<> = "The Batman is my name"
IO.inspect(article, label: "2 - article")
IO.inspect(name, label: "2 - name")
IO.inspect(rest, label: "2 - rest")
1: "Batman"
2 - article: "The"
2 - name: "Batman"
2 - rest: " is my name"
The Pin Operator
The Pin ^
operator allows you to match against the value of an existing variable:
name = "Peter"
fun = fn
%{name: ^name} -> "That's me!"
%{name: _name} -> "That's not me."
end
fun.(%{name: "Peter"}) # => "That's me!"
fun.(%{name: "Bob"}) # => "That's not me."
Assignment is Matching
Fun fact: In Elixir there is no such thing as a “variable assignment”. It’s match! When you assign a value to a variable, you actually “match” the value against the variable and because that match is always true, the variable will continue to hold the value. This becomes clearer when we pin the variable:
a = 1
a = 2
^a = 3
** (MatchError) no match of right hand side value: 3
(stdlib 6.0) erl_eval.erl:652: :erl_eval.expr/6
Interesting! When we pin the variable a
and try to assign a new value to it, it raises a MatchError
! This happens because a
holds the value 2
when we try to match it agains the value 3
, which creates a mismatch (Got it? Mis-match! 😬). This shows that the =
operator is actually a match, not an assignment. Today you learned!