Pattern matching in Elixir

Krishna rao
PathFactory
Published in
5 min readApr 9, 2019

Pattern matching is an integral feature of functional programming and gives us a way to compare data against certain patterns. It can be used to assign values to variables and to control the flow of the code. Since Elixir is a functional language, it natively implements this amazing feature.

Typically in OOP languages, = is used to assign values to variables, whereas in Elixir, = is defined as a match operator. This is similar to the mathematical idea of equivalence. Consider the following example:

[x, 2] = [1, 2]

This would be the same as stating that the left-hand side is equivalent to the right-hand side. Following this principle, the value of x is determined to be 1. In Elixir, this allows us to assign values to a variable by matching data like lists, maps, and tuples.

Pattern matching with Lists

Pattern matching can be used to destructure a list in order to extract certain values into variables.

There are 2 ways to destructure lists.

[first, second] = [1, 2]
# first = 1, second = 2
[first, second, third] = [1, 2]
# ** (MatchError) no match of right hand side value: [1, 2]
[first, second] = [1, 2, 3]
# ** (MatchError) no match of right hand side value: [1, 2, 3]

As long as the shape of the list matches the pattern, the variables would get assigned the corresponding values. As seen in the last example, if the list size is smaller or larger than the number of arguments in the pattern, a MatchError would be raised.

We can also destructure by matching the head and tail of a list.

[head | tail] = [1, 2, 3] 
# head = 1, tail = [2, 3]
[first, second | rest] = [1, 2, 3]
# first = 1, second = 2, rest = [3]
[first, second | rest] = [1]
# ** (MatchError) no match of right hand side value: [1]

Pattern Matching with Maps

Pattern matching also provides us with an easy way to extract values from a map and assign to a variable.

%{name: name} = %{name: "Krishna", city: "Toronto"}
# name = "Krishna"
%{email: email} = %{name: "Krishna"}
# ** (MatchError) no match of right hand side value: %{name: "Krishna"}

As evident, all keys specified on the left should exist in the data being matched on the right. For example, this can be used to validate data returned from an external API to ensure that it contains all the fields the domain cares about. In case of invalid data, a MatchError will be thrown, and can be handled by the caller.

Pattern Matching with Tuples

Tuples are typically used in Elixir for

  • storing multiple values of different types in the same data structure
  • error handling

Pattern matching multiple values from a tuple

{name, age, interests} = {"Krishna", 22, ["Elixir", "Elm"]}
# name = "Krishna", age = 22, interests = ["Elixir", "Elm"]
{name, age, interests} = {"Krishna"}
# ** (MatchError) no match of right hand side value: {"Krishna"}

The number of elements in a tuple should be the same on the LHS as the RHS for the pattern match to succeed.

Using pattern matching to handle errors

It is common for Elixir functions to return a tuple to indicate whether an operation succeeded or not.

Imagine there is a function that returns :ok if a record is successfully saved or {:error, error_message} if the record is invalid.

save_record(valid_record)
# :ok
save_record(invalid_record)
# {:error, "Could not save record due to validation errors"}

The caller can then pattern match the response to either get the value or handle the error.

case save_record(record) do
:ok
-> IO.puts("Record successfully saved!")
{:error, error_msg}
-> IO.puts("Could not save record because of #{error_msg}")
end

Pattern matching with Functions

Method overloading is a common feature in OOP and similar functionality can be implemented in Elixir by pattern matching the arguments.

To handle valid and invalid cases, save can be defined as follows

def save({:ok, valid_data}) do
# Proceed to save the data
end
def save({:error, error}) do
# Skip and log the error
end

Consider a validate function that takes a record and returns a tuple to indicate whether it is valid or not. We could then pipe the results of that function into the save function, and pattern match the result in order to proceed with saving the data or handling the error.

validate(data) |> save

An important thing to note is that the first function clause to match the pattern will be executed.

In the example below, the second function clause will never be executed (because the first clause will always match)

def process_data(data) do
# process the data
{:ok, processed_data}
end
def process_data([]) do
{:error, "No data provided"} # This will never be executed!
end

Pattern matching with an existing variable

Consider an example where we want to execute a method only if the map contains a specific key (and the key is specified dynamically). In this case, we could hold the key in a variable and we can pattern match using the pin operator ^.

key = "name"
parameters = %{"name" => "Krishna"}
case parameters do
%{^key => name} -> save(name)
_ -> IO.inspect "Name not present in the parameter"
end

In this example, since the parameters object contains the name key, the pattern match would succeed and the save function would be invoked.

Discarding values when pattern matching

There might be cases where we want to match against a specific value and do not care about other values being matched. Elixir has a special wildcard character _ to ignore such values.

{id, details, _} = {1, %{name: "Krishna", city: "Toronto"}, 22}

In this example, if the data contains 3 values but we want to process only the first 2 values, we can ignore the third value by matching it against a _.

We could also name the discarded value for readability by starting the variable name with an underscore, though it’s not possible to access this variable afterwards.

{id, details, _age} = {1, %{name: "Krishna", city: "Toronto"}, 22}

Summary

Pattern matching is a simple, yet powerful feature in Elixir that can be used to control the flow of execution as well as assign values to variables. It provides an easier way to retrieve values and assign them without using functions like Enum.at(enum, index) or elem(tuple, index).

For example, it is very common to match the return value of a database operation and return either a value or an error message to the user. Pattern Matching provides a clean way to handle errors as most of the core functions in Elixir and corresponding hex packages return a tuple to indicate whether it was successful or not. We can handle the errors by matching this return value without requiring complex rescue operations such as(try, catch and rescue).

Pattern matching is easy to use and a very useful feature of Elixir. Due to its declarative nature, using it can make your code more readable and maintainable.

--

--