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}

2 Likes

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.

6 Likes

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.

6 Likes

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.

1 Like

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

4 Likes

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.

8 Likes

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

1 Like

Thanks, that makes it even clearer.

2 Likes

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.

1 Like

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.

2 Likes

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.

2 Likes

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.

3 Likes

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

1 Like

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