Pattern Matching
You’ve seen the = operator used to assign a value to the variable. = in Elixir is called the match operator. As it turns out, it can do much more than just a simple assignment!
Basic Matching
Matching identical values:
1 = 1
Assigning to a variable:
x = 1
After the 1 is assigned to x, it can be used for matching. Line 1 = x is legal and means “match x against pattern 1”, which succeeds.
1 = x
Test what happens when the pattern is 2, or when you try to match a variable before it has been assigned. Experiment with mismatched patterns to see the difference between runtime and compilation errors.
x = 2
# 💡 Try uncommenting the following lines. What happens?
# 2 = x
# 2 = new_variable
Matching Tuples
Pattern matching lets you extract tuple elements into variables. The pattern must match the tuple size.
Extracting tuple elements:
{a, b} = {:hello, "world"}
IO.inspect(a, label: "a")
IO.inspect(b, label: "b")
The pattern must be of the same size for the match to succeed:
# ❗️ This code raises an error!
# 💡 Let's modify the pattern on the left to fix the error
{x, y} = {:hello, "world", 42}
IO.inspect(x, label: "x")
IO.inspect(y, label: "y")
In Elixir, functions commonly return {:ok, result} on success and {:error, reason} on failure. You can use atoms in patterns to match such results:
# 💡 Find out what happens if you replace `:answer`
# with a different atom
{:ok, answer} = Map.fetch(%{answer: 42}, :answer)
answer
Matching Lists
List matching works similarly to tuples - you can extract every element of the list. The sizes of pattern and the value must equal for a successful matching.
Extracting list elements:
[j, k, l] = [1, 2, 3]
IO.inspect(j, label: "j")
IO.inspect(k, label: "k")
IO.inspect(l, label: "l")
The cons operator (|, see the lists section) provides a special pattern for matching the head (first element) and tail (remaining elements).
# 💡 Try to match empty (`[]`) or single element list (`[1]`)
[head | tail] = [1, 2, 3]
IO.inspect(head, label: "head")
IO.inspect(tail, label: "tail")
This shows how lists are internally structured using the cons operator - each element is prepended to the rest of the list:
[head | tail] = [1 | [2 | [3 | []]]]
IO.inspect(head, label: "head")
IO.inspect(tail, label: "tail")
You can use the destructured parts to create new lists:
[head | tail] = [1, 2, 3]
[0 | tail]
Matching Maps
Patterns for maps can extract the value of any key or ensure that a specified key has a provided value. The pattern doesn’t have to be exhaustive and cover every entry stored inside the map.
Extracting a value by key:
map = %{"a" => 1, :b => 2, :c => "c"}
%{"a" => one} = map
one
An empty map pattern matches any map:
%{} = %{}
%{} = map
You can enforce specific values for keys:
# 💡 Try changing "c" to something else and see the result
%{:b => two, :c => "c"} = map
two
For atom keys, a special syntax is available:
%{c: cee} = map
cee
Special patterns
Elixir provides additional constructs for more precise pattern matching control.
The pin operator
In Elixir, variables can be rebound, i.e. assigned to a new value. If you want to avoid that in a pattern and match against the actual value assigned to that variable, you must use the pin ^ operator. It can be a part of more complex patterns.
Variables can be rebound:
x = 1
x = 2
x
Using the pin operator to match against current value:
# 💡 Let's see what will happen if you change the value to 3
x = 2
# This is the equivalent of
^x = 2
The pin operator works in complex patterns:
{y, ^x} = {3, 2}
{y, x}
Repeated variables
If a variable occurs more than once in the pattern, it must bind to the same value. Otherwise, the match will fail.
# 💡 Try changing one of the values in tuple on the right
{z, z} = {3, 3}
z
Ignore pattern
If you only want to extract some of the values, you may use the ignore pattern: _. It is a special variable that can never be read from
# 💡 Let's try using the underscore
[head | _] = [1, 2, 3]
head
You can use named ignore patterns (like _reason) for readability, though reading them yields a warning:
{:error, _reason} = {:error, :enoent}
# 💡 Let's try using _reason
:ok