Pattern Match On Empty Maps

I’m really confused why the an empty map pattern matches with a random other map

%{} = %{foo: :bar}

but now when set to a variable

empty_map = %{}
^empty_map = %{foo: :bar}
** (MatchError) no match of right hand side value: %{foo: :bar}
    (stdlib) erl_eval.erl:453: :erl_eval.expr/5
    (iex) lib/iex/evaluator.ex:257: IEx.Evaluator.handle_eval/5
    (iex) lib/iex/evaluator.ex:237: IEx.Evaluator.do_eval/3
    (iex) lib/iex/evaluator.ex:215: IEx.Evaluator.eval/3
    (iex) lib/iex/evaluator.ex:103: IEx.Evaluator.loop/1
    (iex) lib/iex/evaluator.ex:27: IEx.Evaluator.init/4

Reproducible on Mac OSX with the following Elixir/Erlang

Erlang/OTP 22 [erts-10.7.2.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Interactive Elixir (1.9.4) - press Ctrl+C to exit (type h() ENTER for help)

Reproducible on Pop!_OS too. I guess a pinned variable expects an exact match.

my_map = %{foo: :bar}
^my_map = %{foo: :bar, baz: :qux}
** (MatchError) no match of right hand side value: %{baz: :qux, foo: :bar}

This behaviour shows in Erlang, too

#{foo := bar} = #{foo => bar, baz => qux}.
returns #{baz => qux,foo => bar}

MyMap = #{foo => bar}.
MyMap = #{foo => bar, baz => qux}.
** exception error: no match of right hand side value #{baz => qux,foo => bar}

This happens because the pin is used to match a value in a pattern. The value in this case is %{} and the RHS doesn’t match it. Patterns are not first class in Elixir or Erlang, so you cannot store them in variables and use for matching.

Map patterns match at least the given keys, so an empty map will match any map.

If you want to pattern match an empty map, you can use the guard when map_size(a_map) == 0.

Or do when map == %{} which has the same effect.

Thanks for the help. Nicd points out the thing I was missing in that patterns aren’t first class.

Anyone who comes here in the future who wants to have a function with a dynamic match this is how I ended up doing it

def get_items(filters \\ %{}) do
  filters = MapSet.new(filters)

  for item <- get_all_items(),
    MapSet.subset?(filters, MapSet.new(item)),
    do: item
end

The effect is the same but strict comparison should be more efficient I reckon.