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 } |
Is there an entry with the key :name?
| iex> %{ name: a_name } = person |
| %{height: 1.88, name: "Dave"} |
| iex> a_name |
| "Dave" |
Are there entries for the keys :name and :height?
| iex> %{ name: _, height: _ } = person |
| %{height: 1.88, name: "Dave"} |
Does the entry with key :name have the value "Dave"?
| iex> %{ name: "Dave" } = person |
| %{height: 1.88, name: "Dave"} |
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.
| 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.
| 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 |
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… |
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"] |