Pattern match function against empty map

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?


Solution 1:

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:

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