Elixir from the ground up

Pattern matching: the equals sign that matches

In Elixir the equals sign does not mean "put this in that" — it means "make these two fit". That single idea does the work other languages hand to destructuring, type checks and switches.

Part 3 of 12

Beginner16 min readBack to Courses

The equals sign is a match

In most languages x = 1 means assign 1 to x. In Elixir it means match: take the value on the right, compare it with the shape on the left, and make the two agree. x = 1 agrees by binding x to 1. {a, b} = {1, 2} agrees by binding a to 1 and b to 2 — both names come out of one line. The left side is a pattern, not a value being stored, which is why it can hold names that do not exist yet and shapes that describe the right side.

The shape can be as specific as you like. {:ok, value} = {:ok, 42} binds value to 42, and the same line on a value of {:error, "nope"} refuses to match at all. That refusal is the point: instead of checking that a function succeeded, you state the shape you expected and let the match enforce it. The failure is not a quiet nil: it raises a MatchError and stops the script, because carrying on with a value that does not fit would make every later line a guess.

Pulling a value apart

A match is how you take a value apart without asking it questions. A list is a head plus a tail, so [head | tail] = [10, 20, 30] gives head 10 and tail [20, 30]. A map is a set of named values, so %{name: name} = user pulls out just the name and ignores the rest. A list pattern can name the first item and keep the rest as one value, and the tail is always a list, even when it holds a single item.

Anything you do not want a name for is ignored in the pattern, which is how you say "I know something is here, and I am not asking what it is". A name beginning with an underscore, such as _value, is the explicit version: it matches anything, and it tells the reader that the ignoring was meant.

The pin, and matches that fail

Once a name holds a value, writing that name again on the left would rebind it. To compare instead, put a pin in front: ^expected. The pin means "this spot must equal whatever the name already holds". The pin is how a pattern asks a question rather than making a new name, and it works anywhere a name can appear, including inside a map or a nested tuple.

A failed match raises an error and stops the program, which is deliberate — the pattern is a promise about the shape of the data, and Elixir would rather stop than carry on with a value that does not fit. When you are not sure, match? asks the question without the drama and answers true or false.

Matching values apart

# The equals sign means "make this fit".
{a, b} = {1, 2}
IO.inspect({a, b})

[head | tail] = [10, 20, 30]
IO.inspect(head)
IO.inspect(tail)

{:ok, value} = {:ok, 42}
IO.inspect(value)

# A pin keeps the value the name already holds.
expected = 5
{^expected, other} = {5, 9}
IO.inspect(other)

# When a match fails the program stops, so ask first.
IO.inspect(match?({:ok, _value}, {:error, "no"}))

One line, several values

Run it and follow each line back to the value it took apart. Then write a match that pulls the second and third items off a list, and use the pin to check a value you have already named.

Output
Click "Run" to execute your code

A match that does not fit raises and the program stops, so a bare {:ok, value} = answer is a promise you are making about the answer. When the shape is not certain, ask with match? or choose a path with case instead.

The right side is evaluated first

When you write a match, Elixir evaluates the right side to a value and then tests the left side against that value. The left side is not code that runs — it is a description of a shape. That is why %{name: name} = user works even though the name appears on the right: the map is read first, and the pattern then pulls the name out of it. That order is why a pattern can use a name on both sides of the equals sign without confusion: the right side is a value first, and the left side is a description applied to it.

A map pattern is partial on purpose: %{name: name} = user asks only that the key is present, and ignores every other key. A tuple pattern is exact: {a, b} demands a two-element tuple. Choosing the container is choosing how strict the promise is.

A name on the left rebinds silently, so x = 5 after x = 1 is not an error — use ^x when you mean to compare. A failed match raises MatchError and stops the script, so do not promise a shape you have not checked. And an underscore name such as _value still binds: the underscore only tells the reader you do not intend to use it.

Try these next

  • Match a three-element tuple in one line and print the middle value.
  • Build a map with a name and a role, then pull the role out with a map pattern.
  • Use match? to ask whether {:ok, 1} fits {:error, _} and print the answer.

Unpacking an order

Run it and follow the first match: it pulls three values out of a tuple that holds a map. Then change the quantity, add a second item to the tail, and write a match that takes the third and fourth items of a four-item list.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does {a, b} = {1, 2} do?