Pattern matching in Elixir
When I first began learning Elixir I was caught out by its pattern matching. Speaking to other Elixir students and hanging out in the Slack channel, it was clear I wasn’t alone.
Isn’t it just assignment?
Syntactically Elixir’s pattern matching can appear identical to assignment in other languages.
> a_var = “a value”
“a value”# check a_var holds “a value”
> a_var === “a value”
true
In the code above, Elixir will use pattern matching to determine if in fact a_var can be assigned the value “a value”. This example is as simple as it gets, so no difference in implementation to assignment.
So how is it different?
In Elixir pattern matching is a type of assertion. It calls ‘=’ a match operator and it works by determining if the left-hand side can be made equal to the right-hand side. The last statement bears repeating, can Elixir make the left- hand side equal to the right-hand side?
Let’s look at an example from Elixir’s introductory guide.
> {a, b, c} = {:hello, “world”, 42}
{:hello, “world”, 42}> a
:hello> b
“world”> c
42
Here the right-hand side of the match operator, =, is a tuple. It has three elements, an atom, a string and an integer. Ok so far.
Now, in order to make the left-hand side equal to the right we’d need to have a three element tuple on the left with with either identical values or ‘placeholders’, variables that can be assigned. Elixir does this by assigning the variables a, b & c into them. We have a match!
The variables now contain the matching element from the right-hand side. This is a form of decomposition seen in other languages.
In contrast to this, if the tuples have a different number of elements (imagine you are not interested in the last integer being passed) there is a MatchError
> {a, b} = {:hello, “world”, 42}
** (MatchError) no match of right hand side value: {:hello, “world”, 42}
In this case, you can pass an underscore on the left-hand side and Elixir will immediately discard the value it matches, while still allowing the match to take place.
> {a, b, _} = {:hello, “world”, 42}
{:hello, “world”, 42}
Taking this one step further, let’s change up the example slightly.
> {:hello, b, c} = {:hello, “world”, 42}
{:hello, “world”, 42}> b
“world”> c
42
Here, we’ve hard-coded the first element of the left-hand tuple to :hello. The pattern matching remains the same, can it make the left equal to the right? Here it can, and two variables are created, b and c. This was the start of my understanding of why pattern matching exists.
Why is this special?
The real beauty of pattern matching comes when you use it in combination with other aspects of the language.
For a contrived example let’s use a tuple as a parameter to a function. The tuple will be passed to the converse/1 function in our Chatter module.
I’m using another feature of the language here, declaring three function definitions with the same name and arity (how many arguments it takes e.g converse/1), that’s a whole other post (a basis for recursion etc…), for now let’s work through the example.
I can call the converse/1 function thus,
> Chatter.converse({:hello, “Stephanie”, “World Bank”})
# Hi Stephanie. Nice to meet you. I hear you work for World Bank.> Chatter.converse({:hello, “Trevor”, “Local Bank”})
# Hi Trevor. Nice to meet you. I hear you work for Local Bank.> Chatter.converse({:small_talk, “Stephanie”, “fishing”})
# Hey Stephanie, have you been doing much fishing lately?> Chatter.converse({:goodbye, “Trevor”})
# Trevor, it was great to talk to you today, goodbye.
It’s contrived, but here you can see we have allowed for three different types of conversation without any conditionals in our code. Each of the function signatures clearly show their intent through the first element of the tuple. Our code is simplified.
An example from Phoenix
In the previous example when the function is called, pattern matching kicks in. Let’s imagine in pseudo-code the params getting matched.
{:hello, name, employer} = {:hello, “Stephanie”, “World Bank”}
So, the parameters to the function call become the ‘right-hand side’ and arguments in the function signature are the ‘left’. Simple. Straightforward.
Now. When I first used Phoenix I saw something I found confusing in some function signatures. Here’s an example from the show action of a controller.
def show(conn, %{“user_id” => user_id} = params) do
# … show stuff here using variables user_id and params
end
Hmmm. This show/2 function takes two parameters, but, in the signature there appears to be some pattern matching going on, this confused me.
The explanation is quite simple. Elixir is pattern matching params first (the passed in map is the right-hand side, params becomes the left), then pattern matches user_id, as the left-hand side, against params which is now the right-hand side, like so.
%{“user_id” => user_id} = params = <map passed in># breaks down to
params = <map passed in># then to
%{“user_id” => user_id} = params
As a result of this you have access to the full params map, and a separate user_id in the function body. This is another example of decomposition.
Once I had the concept of the passed arguments coming into the right-hand side, the code immediately became clearer and understandable.
NOTE: You would be right in thinking the params map will almost always have more than one key/value entry in it. Looking back on the tuple example, if the left-hand side didn’t have the same number of elements as the right, a MatchError was thrown. Maps are different. The reason for this is tuple lengths are idiomatically ‘set’ and commonly have up to four elements, whereas a map is more arbitrary in the number of members it may contain.
Plus, the designers of the language wanted an easy way to pluck a value from a map, it’s a common need and simplifies the process.
Hopefully this will help you better understand pattern matching. Allowing you to write more expressive, simpler code.