I don't understand how %{:a => a} = %{:a => 1, 2 => :b} can match

Hello, my first post. I’m head first into learning Elixir and I’m really enjoying it.

I’m struggling to see how the code below matches. I would have thought %{:a => 1} would be the return value not %{2 => :b, :a => 1}

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

%{2 => :b, :a => 1}

The match operator =/2 returns the expression from the right hand side or it fails completely.

So after your match a will be bound to 1.

a is bound to 1 but as @NobbZ said, the right hand is returned to allow stuff like:

map = %{a: a} = %{:a => 1, 2 => :b}

In this case the map will be whole map and a will be bound to 1 as it was earlier.

Would it be correct to say that there is a match if the totality of each Map matches or if one or more key value pairs match within the other Map.

Left side match must match subset of keys of the map on the left.

Left side match must match subset of keys of the map on the left.

This also means that %{} matches any map, not only empty ones.

Thanks, got it. My confusion was because I though the total image of both Maps must line up.

Thanks, that makes it even clearer.

Maps are one exception to the general pattern matching rules, e.g. as opposed to matching lists and tuples:

It might be nice to explicitly mention that in this chapter of the official Getting Started guide:

It is mentioned in a subsequent chapter:

But it might be nice to repeat that info in other places too.

It’s confusing because maps are the only thing that can do such an inexplicit partial match. Be careful when doing say assert %{} =. On the other hand you can imagine why it’s more useful to do matching on map partials than on fully specified maps; and in general erlangs/elixir choices are largely driven by utilitarianism more than consistency.

Also because if %{} matched exactly empty maps then you would need to use a guard to match on any non empty map when not specifying keys (is_map) and those were only added later. Also, I would usually say that we pattern match much more commonly on the map form with or without any keys than on a map being empty (as such it’s much more useful as you mention), as that is probably a sign that those functions are not using the appropriate data structure if they care about a map being empty.

Matching maps could have been done similar to how nix deals with it in attribute sets.

A key specified requires it to be present.
A key not specified disallows it do be present, unless the special key ... is given, which allows for arbitrary additional keys beeing present.

Transfered to elixir this would mean:

%{} matches the empty map, %{...} matches any map (including the empty)
%{foo: _} matches the map that has only the :foo key, while %{foo: _, ...} would match any map that has at least the :foo key.

The problem: Matching #{} as empty map is not even allowed on the BEAM. So elixir can not support a syntax as expressed above, unless erlang/OTP introduces a way of matching the empty map without guards.

Yeah, the empty map could see a lot of use in tests at least (and some regular code too occasionally), the others I think they are more or less covered by just having structs - I also don’t think it’s a good option to allow bare maps in a program drive flow according to if they have a key or not - if something depends on keys being present it makes more sense to me to cast that to a proper struct/ecto/record than having its meaning codified by fields being present or missing from it. It would also be a breaking change to introduce the exact keys match syntax - although if it was there from the beginning it would probably see a lot of usage but personally I only “miss” the empty map match.

just to clarify Matching #{} as empty map is not even allowed on the BEAM doesn’t mean you can’t match empty map. You can use a with clause to achieve this:

match?(m when m == %{}, value)

for example works just fine, as it does inside case clauses, or in function guards. You can also assign to a variable and then use the pin operator.

Possibly surprisingly, using an @ attribute does not work by “subbing as a variable and pinning” first, which I think is one of the few footguns in elixir.

A guard clause you mean, but guards are not pattern matches.

A pattern match can be reordered within limits. Binding to a name and then checking conditions in a guard can’t. Even worse, they make it impossible for the BEAM to reorder matches on each side of the clause with the guard

I’m using “match” in the broadest sense. In elixir we have the match?/2 macro, after all.