Pattern match function against empty map

Pattern MatchingElixir

Pattern Matching Problem Overview


I'm playing around with pattern match and I found out, that it's not quite easy to pattern match parameters of a method against an empty map. I thought it would go something like this:

defmodule PatternMatch do
  def modify(%{}) do
    %{}
  end

  def modify(map) do
    # expensive operation
    %{ modified: "map" }
  end
end

But it seems like the first function clause matches arbitrary maps:

iex> PatternMatch.modify(%{a: "map"})
==> %{}

Is there another way to check for empty maps?

Pattern Matching Solutions


Solution 1 - Pattern Matching

It works this way by design, but admittedly it can be a bit confusing at first glance. This feature allows you to destructure maps using pattern matching, without having to specify all keys. For example:

iex> %{b: value} = %{a: 1, b: 2, c: 3}
%{a: 1, b: 2, c: 3}

iex> value
2

Consequently, %{} will match any map. If you want to match an empty map in a function, you have to use a guard clause:

defmodule PatternMatch do
  def modify(map) when map == %{} do
    %{}
  end

  def modify(map) do
    # ...
  end
end

Solution 2 - Pattern Matching

In addition to @PatrickOscity's answer (which I would use for an empty map), you can use a map_size/1 guard to match against maps with a number of keys:

defmodule PatternMatch do
  def modify(map) when map_size(map) == 0 do
    %{}
  end

  def modify(map) when map_size(map) == 1 do
    #something else
  end

  def modify(map) do
    # expensive operation
    %{ modified: "map" }
  end
end

Here is an output from iex using Kernel.match?/2 to show map_size/1 in action:

iex(6)> Kernel.match?(map when map_size(map) == 1, %{})
false
iex(7)> Kernel.match?(map when map_size(map) == 1, %{foo: "bar"})
true

Solution 3 - Pattern Matching

In addition to all the cool answers provided so far, you may also consider the usage of the unary pin operator that looks like a hat or an upper arrow point. You use it to prefix a variable with it to ensure you pattern match against its value, as stated in the relevant documentation:

> Use the pin operator ^ when you want to pattern match against an > existing variable’s value rather than rebinding the variable

Following is an example:

defmodule A do
  def determine_map_volume(some_map) do
    an_empty_map = %{}

    some_map
    |> case do
    ^an_empty_map -> :empty  # Application of pin operator
    _ -> :not_empty
    end
  end
end

Which you can verify as follows:

A.determine_map_volume(%{})
:empty
A.determine_map_volume(%{a: 1})
:not_empty

Which method you intend to use depends on your personal/organizational preference for the readability of your code.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionleifgView Question on Stackoverflow
Solution 1 - Pattern MatchingPatrick OscityView Answer on Stackoverflow
Solution 2 - Pattern MatchingGazlerView Answer on Stackoverflow
Solution 3 - Pattern MatchingKevin JohnsonView Answer on Stackoverflow