Pattern Matching and Updating Maps

The question we most often ask of our maps is, “Do you have the following keys (and maybe values)?” For example, given this map:

 person = %{ ​name:​ ​"​​Dave"​, ​height:​ 1.88 }

Our map does not have the key :weight, so the following pattern match fails:

 iex>​ %{ ​name:​ _, ​weight:​ _ } = person
 **​ (MatchError) no match of right hand side value: %{height: 1.88, name: "Dave"}

It’s worth noting how the first pattern match destructured the map, extracting the value associated with the key :name. We can use this in many ways. Here’s one example. The for construct lets us iterate over a collection, filtering as we go. We cover it when we talk about enumerating. The following example uses for to iterate over a list of people. Destructuring is used to extract the height value, which is used to filter the results.

maps/query.exs
 people = [
  %{ ​name:​ ​"​​Grumpy"​, ​height:​ 1.24 },
  %{ ​name:​ ​"​​Dave"​, ​height:​ 1.88 },
  %{ ​name:​ ​"​​Dopey"​, ​height:​ 1.32 },
  %{ ​name:​ ​"​​Shaquille"​, ​height:​ 2.16 },
  %{ ​name:​ ​"​​Sneezy"​, ​height:​ 1.28 }
 ]
 
 IO.inspect(for person = %{ ​height:​ height } <- people, height > 1.5, ​do​: person)

This produces

 [%{​height:​ 1.88, ​name:​ ​"​​Dave"​}, %{​height:​ 2.16, ​name:​ ​"​​Shaquille"​}]

In this code, we feed a list of maps to our comprehension. The generator clause binds each map (as a whole) to person and binds the height from that map to height. The filter selects only those maps where the height exceeds 1.5, and the do block returns the people that match. The comprehension as a whole returns a list of these people, which IO.inspect prints.

Clearly pattern matching is just pattern matching, so this maps capability works equally well in cond expressions, function head matching, and any other circumstances in which patterns are used.

maps/book_room.exs
 defmodule​ HotelRoom ​do
 
 def​ book(%{​name:​ name, ​height:​ height})
 when​ height > 1.9 ​do
  IO.puts ​"​​Need extra-long bed for ​​#{​name​}​​"
 end
 
 def​ book(%{​name:​ name, ​height:​ height})
 when​ height < 1.3 ​do
  IO.puts ​"​​Need low shower controls for ​​#{​name​}​​"
 end
 
 def​ book(person) ​do
  IO.puts ​"​​Need regular bed for ​​#{​person.name​}​​"
 end
 
 end
 
 people |> Enum.each(&HotelRoom.book/1)
 
 #=> Need low shower controls for Grumpy
 # Need regular bed for Dave
 # Need regular bed for Dopey
 # Need extra-long bed for Shaquille
 # Need low shower controls for Sneezy

Pattern Matching Can’t Bind Keys

You can’t bind a value to a key during pattern matching. You can write this:

 iex> %{ 2 => state } = %{ 1 => ​:ok​, 2 => ​:error​ }
 %{1 => ​:ok​, 2 => ​:error​}
 iex> state
 :error

but not this:

 iex>​ %{ item => :ok } = %{ 1 => :ok, 2 => :error }
 ** (CompileError) iex:5: illegal use of variable item in map key…

Pattern Matching Can Match Variable Keys

When we looked at basic pattern matching, we saw that the pin operator uses the value already in a variable on the left-hand side of a match. We can do the same with the keys of a map:

 iex>​ data = %{ ​name:​ ​"​​Dave"​, ​state:​ ​"​​TX"​, ​likes:​ ​"​​Elixir"​ }
 %{likes: "Elixir", name: "Dave", state: "TX"}
 iex>​ for key <- [ ​:name​, ​:likes​ ] ​do
 ...>​ %{ ^key => value } = data
 ...>​ value
 ...>​ ​end
 ["Dave", "Elixir"]