Elixir: Pattern Matching
In Elixir the =
symbol is not for assignment but used as an operator for pattern-matching. This means that when a variable name is matched to some data it can be rematched, just like in other languages variables are reassigned, but there are some differences. For example:
iex> a = "hello"
#=> "hello"
iex> b = "goodbye"
#=> "goodbye"
iex> a = b
#=> "goodbye"
iex> a
#=> "goodbye"
iex> "goodbye" = a
#=> "goodbye"
iex> "caramel" = b
#=> ** (MatchError) no match of right hand side value: "goodbye"
As you can see in the last two examples, this is not assignment. Assignment would throw an error for expression "goodbye" = a
, because it would instead expect an expression of equality, such as "goodbye" == a
(returning a boolean). However with Elixir’s pattern-matching it is checking that the right and left-hand sides are the same. It is only when they are not that an error is raised, because "caramel"
does not match "goodbye"
.
Pattern-matching on Collections
Collections can be pattern matched on using a pipe operator, like so:
iex> list_of_numbers = [1, 2, 3, 4, 5]
#=> [1, 2, 3, 4, 5]
iex> [ first_element | remaining_elements ] = list_of_numbers
#=> [1, 2, 3, 4, 5]
iex> first_element
#=> 1
iex> remaining_elements
#=> [2, 3, 4, 5]
The pipe operator, or |
, is splitting the first element of a list with the remaining elements. This is because an Elixir list is a linked-list, and so [1, 2, 3]
can be thought of as [1 | [2 | [3]]]
. Therefore we could also use a pipe operator like so:
iex> list_of_numbers = [1, 2, 3, 4, 5]
#=> [1, 2, 3, 4, 5]
iex> [ first, second | remaining ] = list_of_numbers
#=> [1, 2, 3, 4, 5]
iex> first
#=> 1
iex> second
#=> 2
iex> remaining
#=> [3, 4, 5]
In the same way a list of tuples can be pattern-matched like so:
iex> list_of_tuples = [{"one", 1}, {"two", 2}, {"three", 3}]
#=> [{"one", 1}, {"two", 2}, {"three", 3}]
iex> [{word, digit} | remaining_tuples] = list_of_tuples
#=> [{"one", 1}, {"two", 2}, {"three", 3}]
iex> word
#=> "one"
iex> digit
#=> 1
iex> remaining_tuples
#=> [{"two", 2}, {"three", 3}]
iex> { word, digit }
#=> {"one", 1}
Pattern-matching Arguments
Pattern-matching is very useful in recursion because arguments can be matched again and again. For instance, if we wanted to iterate over a list of elements we could continuously recurse over the remaining elements, which would grow smaller each time. Here is an example:
defmodule Summation do
def sum([]), do: 0
def sum([current_number | remaining_numbers]) do
current_number + sum(remaining_numbers)
end
end
When sum
is called with a list of numbers, it splits the list into current_number
and the remaining_numbers
. It adds the current_number
to the recursive call of the sum
function on the remaining_numbers
. When it is called recursively the remaining_numbers
is treated as a new list to be split into an updated current_number
and an updated remaining_numbers
. This continues until the list is empty i.e. there are no more numbers to recurse over. In this case it matches the function def sum([]), do: 0
. That is, when the list is empty, []
, return 0
to be added to the final sum.
Here we can see how pattern-matching was useful for splitting the current number to be added from the remaining numbers in the list, and how pattern-matching was useful in setting a base case so that when the list matched an empty list the recursion ends.