cult3

Understanding Pattern Matching in Elixir

May 16, 2016

Table of contents:

  1. What is Pattern Matching?
  2. Pattern Matching with Tuples
  3. When the Pattern doesn’t match
  4. Using Pattern Matching in Elixir
  5. Matching Lists
  6. Matching Maps
  7. Wildcard Matching
  8. Matching against the contents of a variable
  9. Conclusion

Pattern Matching was one of the characteristics of Elixir that really got me interested in learning the language. After mostly using PHP, Ruby, and Javascript, Pattern Matching was a totally new revelation to me.

Pattern Matching creates a whole new way of writing code and dealing with common constructs such as if, while, and foreach.

In today’s tutorial we will start to explore the beautiful world of Pattern Matching in Elixir.

What is Pattern Matching?

If you remember back to Understanding the Types in Elixir, when we were talking about variables, we said a value is bound to a variable.

In programming languages such as PHP, Ruby, or Python, you would say that the value is assigned to the variable.

Although it looks like assignment, the = is actually a match operator.

But in fact, the operation is very different. Let’s take a look at the following example:

name = "Philip"

This looks like we’re assigning the value "Philip" to the variable name. But, this is actually Pattern Matching!

When this statement is evaluated at runtime, the left side of the = will be matched with the right side. The left side is known as the pattern, and the right side is the expression. The value of the right side is then bound to the left side.

The mystery of pattern matching is not very well illustrated with variables, so let’s move on to some better examples that will show you the real beauty of Pattern Matching.

Pattern Matching with Tuples

A better illustration of how Pattern Matching works is to use Tuples. If you remember back to Using Tuples in Elixir a Tuple is a “list” type that can hold any value.

For example, we might have the follow tuple:

{"Philip", "Brown"}

We can bound the two elements of this tuple using Pattern Matching:

{first_name, last_name} = {"Philip", "Brown"}

Now the values of the tuple have been bound to the pattern on the left of the =. This means you can access the value using the variables:

first_name
# "Philip"

last_name
# "Brown"

So as you can see, we can use Pattern Matching to decompose the tuple into individual variables. This is because the values on the right side can be bound to the variables on the left.

But what happens when the two sides don’t match?

When the Pattern doesn’t match

If you try to do Pattern Matching where the two sides don’t match you will get an error:

{name} = {"Philip", "Brown"}
# ** (MatchError) no match of right hand side value: {"Philip", "Brown"}

This match fails because the left side has a single variable, but the left side has two values.

The match will also fail if you provide values on the left side that can’t be bound:

{name, true} = {"Philip", "Brown"}
# ** (MatchError) no match of right hand side value: {"Philip", "Brown"}

In this example the value "Brown" can’t be bound to the value true.

However, you can use constants in Pattern Matching:

{name, true} = {"Philip", true}
# {"Philip", true}

In this example the value true matches the second value of the tuple on the left, and so the match is successful.

Using Pattern Matching in Elixir

Pattern Matching is used a lot in Elixir and so we will look at many practical examples over the next couple of weeks.

One of the most prominent uses of Pattern Matching is the return value from a function call. If you remember back to Working with Functions and Modules in Elixir we read a file like this:

File.read("hello.txt")
# {:ok, "Hello World\n"}

And we failed to read an invalid file like this:

File.read("invalid.txt")
# {:error, :enoent}

Returning a tuple from a function call where the first element is an atom is a very common pattern. This allows you to do Pattern Matching:

{:ok, contents} = File.read("hello.txt")

In this example, the first value must match the :ok atom. If this is the case, the contents of the file will be bound to the contents variable.

However, if we try to Pattern Match with an invalid file, we will get an error:

{:ok, contents} = File.read("invalid.txt")

This will cause a Match Error, because the first element of the returned value will be :error, not :ok.

Matching Lists

Matching Lists is basically the same as matching Tuples:

[one, two, three] = [1, 2, 3]

If you remember back to Working with Keyword Lists and Maps in Elixir a common operation used with Lists is to split them into the head and the tail for recursive functions. You can do this via Patter Matching:

[head | tail] = [1, 2, 3]
# [1, 2, 3]

The head variable now contains the first element:

1

And the tail variables contains the rest as a List:

[2, 3]

Matching Maps

Matching Maps works slightly differently to matching against Tuples or Lists. Instead of providing a match for each value of the map, you can just provide the ones you are interested in.

For example:

%{code: code} = %{name: "United Kingdom", code: "UK"}

This will extract the code key of the Map, but notice how I’m not providing a match for the name key.

This is allowed in Elixir because a Map usually contains structured data, and you will often want only a section of that data to work with.

Wildcard Matching

Once you start to do more complex Pattern Matching you will often want to only decompose a single value and ignore the rest. In this case you can use the underscore character as a wildcard:

{_, second, _} = {1, 2, 3}

In this example I’m only interested in the second element of the tuple and so I can use the _ character as a wildcard for the other two elements.

Matching against the contents of a variable

Normally when you match against a variable, the value will always be bound to the variable. For example:

name = "John"
{name, age} = {"Philip", 27}

The name variable is now "Philip" because it was rebound by the pattern match.

But sometimes you will want to match against the contents of the variable. To do that we can use the pin operator ^:

name = "John"
{^name, age} = {"Philip", 27}
# ** (MatchError) no match of right hand side value: {"Philip", 27}

This throws an error because the pattern does not match. This is equivalent to writing:

{"John", age} = {"Philip", 27}

Conclusion

We’ve covered a lot of the basics of Pattern Matching in this tutorial. Pattern Matching seems strange at first when you are used to regular assignment.

But Pattern Matching is a really beautiful operation that makes it possible to write elegant code.

Elixir uses Pattern Matching a lot, and so over the next couple of weeks we will be looking at using Pattern Matching in more depth.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.