Get a boolean whether two maps match or not

I would like to get a boolean if the maps match:

%{foo: :bar} =  %{foo: :bar, hello: :world}

%{foo: :bar} =  %{hello: :world}

The problem is that

  1. I can’t use “==” because both comparisons will return false;
  2. The second assign will raise an error

How can I get a boolean so that the first assign that match will be true, and the latter false?

I think I can achieve that with with:

with %{foo: :bar} <- %{foo: :bar, hello: :world} do
  true
else
  _ -> false
end

Is this the best way to achieve this? Is there a more concise way to write it?

The Kernel.match?/2 function will help:

iex> match? %{foo: :bar}, %{foo: :bar, hello: :world}  
true
iex> match? %{foo: :bar}, %{hello: :world}  
false
6 Likes
match?(%{too: bar}, input)
2 Likes

I got an issue here:

match?(Enum.into([foo: :bar], %{}), Enum.into([foo: :bar, hello: :world], %{}))

** (CompileError) iex:1: cannot invoke remote function Enum.into/2 inside a match

Do you know why this error appears? It explains what I cannot do obviously, but doesn’t explain why.

match?/2 is a macro, which basically expands into a case/2 expression. So in the first argument its only allowed what would be allowed in a case/2 on the left side of a match.

As far as I remember, match/2 even allowed guards using when for the first argument.

iex(1)> match?(x when is_integer(x), 2)
true
iex(2)> match?(x when is_integer(x), :a)
false
2 Likes

match?/2 is actually a macro that compiles to a case expression. The parameters to match?/2 are a pattern and an expression. So the first param needs to be a pattern.

3 Likes

In that case, I don’t know how to achieve what I want…

Enum.into(keyword_list1, %{})
Enum.into(keyword_list2, %{})

How can I know if the first map matches with the second (something like map1 = map2)? I realize that even my with doesn’t work because the first part must be a pattern in a match, as you guys explained.

I converted the keyword lists into maps because I could easily check for matches (e.g. one keyword list is a subset of another); for example: %{foo: :bar} = %{foo: :bar, hello: :world}. It’s a characteristic I like about maps. I guess there’s no way to do it as I intended, and maybe it’s then not even worth to convert into a map.

You can use the pin operator (^/1) to achieve what you want:

left = Map.new(list1)
right = Map.new(list2)

match?(^left, right)
1 Like

It doesn’t seem to work:

iex> map = %{foo: :bar}
%{foo: :bar}

iex> match?(^map, %{foo: :bar, hello: :world})
false

iex> match?(%{foo: :bar}, %{foo: :bar, hello: :world})
true

Patterns aren‘t higher level constructs on the beam. You cannot construct patterns. You can just hardcode them.

3 Likes

Then I misremembered things about how maps match.

Perhaps you can use MapSet to check if one is the subset of another?

1 Like

If you already have keywords, why not work with them directly?

Keyword.equal?(kw1, Keyword.take(kw2, Keyword.keys(kw1)))
3 Likes