View Source Pattern Matching

Pattern matching is extremely powerful in Elixir and you'll use it all the time. Let's see some examples:

Match against Basic Types

# You can match against many data structures, like a map.
# This is a partial match, meaning the 'height' value gets ignored.
%{age: age, name: name} = %{age: 32, name: "Peter", height: 190.47}
IO.inspect([age, name], label: 1)

# Or a Tuple. You can't match partially though.
{a, _, c} = {1, 2, 3}
IO.inspect([a, c], label: 2)

# Or a Keyword list, but you can't match partially either.
[a: a, b: _b, c: c] = [a: 1, b: 2, c: 3]
IO.inspect([a, c], label: 3)

# Or a list, but again, not partially.
[a, b, _] = [1, 2, 3]
IO.inspect([a, b], label: 4)

# But you can match against the head (first element) and tail (the rest) of a list.
[head | tail] = [1, 2, 3]
IO.inspect(head, label: "5 - head")
IO.inspect(tail, label: "5 - tail")
1: [32, "Peter"]
2: [1, 3]
3: [1, 3]
4: [1, 2]
5 - head: 1
5 - tail: [2, 3]

Caveat: Matching against empty Maps

Watch out if you want to match against an empty map, because Elixir will match any map with an empty map, even if the map isn't empty.

defmodule RunElixir.Match do
  def empty?(%{}), do: true
  def empty?(_), do: false
end

RunElixir.Match.empty?(%{}) # => true
RunElixir.Match.empty?(%{a: 1, b: 2}) # => true

# You can either use the 'map_size/1' guard

defmodule RunElixir.MatchSize do
  def empty?(map) when map_size(map) == 0, do: true
  def empty?(_), do: false
end

RunElixir.MatchSize.empty?(%{}) # => true
RunElixir.MatchSize.empty?(%{a: 1, b: 2}) # => false

# Or a comparison to match against an empty map

defmodule RunElixir.MatchEqual do
  def empty?(map) when map == %{}, do: true
  def empty?(_), do: false
end

RunElixir.MatchEqual.empty?(%{}) # => true
RunElixir.MatchEqual.empty?(%{a: 1, b: 2}) # => false

Match in Function Clauses

# You can match (partially) in (anonymous) function clauses.
#
# This here defines an anonymous function with two clauses.
# It's the same as defining two function clauses in a module
# like we've done with 'def empty?()' above, just that
# the function isn't wrapped in a module.
fun = fn
  %{status: status} -> status
  %{age: age} -> age
end

fun.(%{status: :active, height: 190.47}) # => :active
fun.(%{age: 32, height: 190.47}) # => 32

# Or in Module functions
defmodule RunElixir.Profile do
  def details(%{status: status}), do: status
  def details(%{age: age}), do: age
end

RunElixir.Profile.details(%{status: :inactive, height: 190.47}) # => :inactive
RunElixir.Profile.details(%{age: 30, height: 190.47}) # => 30

Match against Strings

# You can match against the end of a string
"My name is " <> name = "My name is Batman"

IO.inspect(name, label: 1)

# Or you can match against specific bytes
#
# This is extremely useful for deconstructing a string into fixed-size byte chunks.
# For example, to implement a communication protocol for which you know that
# the first 3 bytes are one argument, and the next 8 bytes are the second, and so on.
<<article::binary-size(3), _space::binary-size(1), name::binary-size(6), rest::binary>> = "The Batman is my name"

IO.inspect(article, label: "2 - article")
IO.inspect(name, label: "2 - name")
IO.inspect(rest, label: "2 - rest")
1: "Batman"
2 - article: "The"
2 - name: "Batman"
2 - rest: " is my name"

The Pin Operator

The Pin ^ operator allows you to match against the value of an existing variable:

name = "Peter"

fun = fn
  %{name: ^name} -> "That's me!"
  %{name: _name} -> "That's not me."
end

fun.(%{name: "Peter"}) # => "That's me!"
fun.(%{name: "Bob"}) # => "That's not me."

Assignment is Matching

Fun fact: In Elixir there is no such thing as a "variable assignment". It's match! When you assign a value to a variable, you actually "match" the value against the variable and because that match is always true, the variable will continue to hold the value. This becomes clearer when we pin the variable:

a = 1
a = 2
^a = 3
** (MatchError) no match of right hand side value: 3
    (stdlib 6.0) erl_eval.erl:652: :erl_eval.expr/6

Interesting! When we pin the variable a and try to assign a new value to it, it raises a MatchError! This happens because a holds the value 2 when we try to match it agains the value 3, which creates a mismatch (Got it? Mis-match! 😬). This shows that the = operator is actually a match, not an assignment. Today you learned!