Pattern match function against empty map
Pattern MatchingElixirPattern 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.