Primer on Pattern Matching in Elixir

I’ve just started exploring Elixir and realised we can write succinct and readable code in it, thanks to features like pattern matching, piping etc.

Through this article, I’d like to share my understanding of Pattern Matching in Elixir and hope this helps others.

Prerequisites

  • Familiarity with one or more general purpose programming languages like C, Java, Python and understanding of data structures like list, map.

Following is the structure of a Pattern Match in Elixir:

                            LHS = RHS

‘=’ in Elixir is a match operator and not an assignment operator, unlike most of the other programming languages.

Let’s build our pattern matching fundamentals using Elixir primitives.

On Primitives

Elixir has common primitive data types like strings, integers, floats etc.

We can visualise pattern matching as a process where values(RHS) are thrown at a wall after pasting double sided tape patches(LHS) on the wall to catch hold(bind) of a subset(or the entire value) of thrown values.

# 5 is bound to x
iex> x = 5
5
iex>

The same tapes(variables) can be reused to hold another value of a different data type — which is called rebinding.

# x is rebound to the string "hello"
iex> x = "hello"
"hello"
iex>

When a variable is used for the first time, it’s unbounded and it can only be on the LHS, to be bounded.

# z is used for the first time -> unbounded -> can be on LHS
iex> z = 25
25
iex>
# y is used for the first time -> unbounded -> not okay to be on RHS
iex> x = y
** (CompileError) iex:12: undefined function y/0
iex>

Now that the fundamentals of pattern matching are clear, let’s see how it works on various data structures in Elixir.

On Lists

iex> x = 25
25
# a linked list in Elixir is declared using square brackets
iex> list = [1, x, 3]
[1, 25, 3]
# RHS matches with LHS because x's value is 25. a and b are bound to 1, 3 respectively
iex> [a, 25, b] = [1, x, 3]
[1, 25, 3]
iex> a
1
iex> b
3
# Length of lists in both LHS and RHS should match
iex> [a, y, b, c] = [1, x, 3]
** (MatchError) no match of right hand side value: [1, 25, 3]
iex>

Tuples in Elixir are created using curly braces and are stored contiguously in memory. The behaviour of pattern matching on tuples is similar to lists.

On Maps

# A map in Elixir is declared using %{} syntax as shown
iex> map = %{"name" => "alice", "age" => 25, "location" => "India"}
%{"age" => 25, "location" => "India", "name" => "alice"}
iex>
# unlike lists and tuples, where the length should match, a map on the RHS can be matched to a map on the LHS with a subset of keys
iex> %{"name" => name, "age" => age} = map
%{"age" => 25, "location" => "India", "name" => "alice"}
# name would be bound to "alice" as a result of the above match
iex> name
"alice"
iex> age
25
iex>
# if there are keys in LHS that are not in RHS, an error will be thrown
iex> %{"name" => name, "foo" => foo} = map
** (MatchError) no match of right hand side value: %{"age" => 25, "location" => "India", "name" => "alice"}
iex>
# keys in LHS map cannot be unbound variables
iex> %{foo => 25} = %{"age" => 25}
** (CompileError) iex:65: cannot use variable foo as map key inside a pattern. Map keys in patterns can only be literals (such as atoms, strings, tuples, and the like) or an existing variable matched with the pin operator (such as ^some_var)
(stdlib 3.14) lists.erl:1267: :lists.foldl/3
iex>
# It works on nested maps too
iex> %{"a" => %{"b" => bval}} = %{"a" => %{"b" => 98, "c" => 100}}
%{"a" => %{"b" => 98, "c" => 100}}
iex> bval
98
iex>

Pattern matching on Elixir structs works similar to maps.

I’ll cover pin operator, guards, using pattern matching in functions and cases in another article.

References

All Things Tech @StockGro | EX-Glance | EX-D E Shaw