How functions pattern match in Elixir

This blog post was originally published on Elixir School as part of the lesson on Pattern Matching. This lesson is, of course, a collaboration with many other open source contributors over at Elixir School. Please check out the source of the lesson for the full list of contributors, and feel free to put in a pull request to help us keep improving Elixir School for others! And without further interruption, on to the lesson…


Behind the scenes, functions are pattern-matching the arguments that they’re called with.

Say we needed a function to accept a map but we’re only interested in using a particular key. We can pattern-match the argument on the presence of that key like this:

defmodule Greeter1 do
def hello(%{name: person_name}) do
IO.puts “Hello, “ <> person_name
end
end

Now let’s say we have a map describing a person named Fred:

Fred
iex> fred = %{
…> name: “Fred”,
…> age: “95”,
…> favorite_color: “Purple”
…> }

These are the results we’ll get when we call Greeter1.hello/1 with the fred map:

# call with entire map
…> Greeter1.hello(fred)
“Hello, Fred”

What happens when we call the function with a map that doesn’t contain the :name key?

# call without the key we need returns an error
…> Greeter1.hello(%{age: “95”, favorite_color: “Purple”})
** (FunctionClauseError) no function clause matching in Greeter3.hello/1
   The following arguments were given to Greeter3.hello/1:
# 1
%{age: “95”}
    iex:12: Greeter3.hello/1

The reason for this behavior is that Elixir pattern-matches the arguments that a function is called with against the arity the function is defined with.

Let’s think about how the data looks when it arrives to Greeter1.hello/1:

# incoming map
iex> fred = %{
…> name: “Fred”,
…> age: “95”,
…> favorite_color: “Purple”
…> }

Greeter1.hello/1 expects an argument like this:

%{name: person_name}

In Greeter1.hello/1, the map we pass (fred) is evaluated against our argument (%{name: person_name}):

%{name: person_name} = %{name: “Fred”, age: “95”, favorite_color: “Purple”}

It finds that there is a key that corresponds to name in the incoming map. We have a match! And as a result of this successful match, the value of the :name key in the map on the right (i.e. the fred map) is bound to the variable on the left (person_name).

Now, what if we still wanted to assign Fred’s name to person_name but we ALSO want to retain awareness of the entire person map? Let’s say we want to IO.inspect(fred) after we greet him. At this point, because we only pattern-matched the :name key of our map, thus only binding the value of that key to a variable, the function doesn’t have knowledge of the rest of Fred.

In order to retain it, we need to assign that entire map to its own variable for us to be able to use it.

Let’s start a new function:

defmodule Greeter2 do
def hello(%{name: person_name} = person) do
IO.puts “Hello, “ <> person_name
IO.inspect person
end
end

Remember that Elixir will pattern match the argument as it comes in. Therefore in this case, each side will pattern match against the incoming argument and bind to whatever it matches with. Let’s take the right side first:

person = %{name: “Fred”, age: “95”, favorite_color: “Purple”}

Now, person has been evaluated and bound to the entire fred-map. We move on to the next pattern-match:

%{name: person_name} = %{name: “Fred”, age: “95”, favorite_color: “Purple”}

Now this is the same as our original Greeter1 function where we pattern matched the map and only retained Fred’s name. What we’ve achieved is two variables we can use instead of one:

Fred being pattern matched

1. person, referring to %{name: “Fred”, age: “95”, favorite_color: “Purple”}
2. person_name, referring to “Fred”

So now when we call Greeter2.hello/1, we can use all of Fred’s information:

# call with entire person
…> Greeter2.hello(fred)
“Hello, Fred”
%{age: “95”, favorite_color: “Purple”, name: “Fred”}
# call with only the name key
…> Greeter4.hello(%{name: “Fred”})
“Hello, Fred”
%{name: “Fred”}
# call without the name key
…> Greeter4.hello(%{age: “95”, favorite_color: “Purple”})
** (FunctionClauseError) no function clause matching in Greeter2.hello/1
  The following arguments were given to Greeter2.hello/1:
# 1
%{age: “95”, favorite_color: “Purple”}
 iex:15: Greeter2.hello/1

So we’ve seen that Elixir pattern-matches at multiple depths because each argument matches against the incoming data independently, leaving us with the variables to call them by inside our function.

If we switch the order of %{name: person_name} and person in the list, we will get the same result because each are matching to fred on their own.

We swap the variable and the map:

defmodule Greeter3 do
def hello(person = %{name: person_name}) do
IO.puts “Hello, “ <> person_name
IO.inspect person
end
end

And call it with the same data we used in Greeter2.hello/1:

# call with same old Fred
…> Greeter3.hello(fred)
“Hello, Fred”
%{age: “95”, favorite_color: “Purple”, name: “Fred”}

Remember that even though it looks like %{name: person_name} = person} is pattern-matching the %{name: person_name} against the person variable, they’re actually each pattern-matching to the passed-in argument.

Summary: Functions pattern-match the data passed in to each of its arguments independently. We can use this to bind values to separate variables within the function.


Thanks for reading! Want to work on a mission-driven team that loves Elixir and contributing to open source projects? We’re hiring!


Footer top

To learn more about Flatiron School, visit the website, follow us on Facebook and Twitter, and visit us at upcoming events near you.

Flatiron School is a proud member of the WeWork family. Check out our sister technology blogs WeWork Technology and Making Meetup.

Footer bottom