Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Pattern Matching

guides/01-basics/pattern-matching.livemd

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.

map_empty? = fn
  %{} -> true
  _ -> false
end

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

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

map_empty_guard? = fn
  map when map_size(map) == 0 -> true
  _ -> false
end

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

# Or a comparison to match against an empty map

map_empty_comparison? = fn
  map when map == %{} -> true
  _ -> false
end

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

Match in Function Clauses

# You can match (partially) in function clauses:
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.
<> = "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!