Matching argument and map key in function parameters

We can match arguments like this in elixir:

iex(1)> f = fn(a, [b: a]) -> a end                                                   
#Function<12.99386804/2 in :erl_eval.expr/5>
iex(2)> f.(:me, b: :me)                               
:me
iex(3)> f.(:me, a: :me)
** (FunctionClauseError)

We can even match on keys of Keywords:

iex(3)> f = fn(a, [{a, a}]) -> a end 
#Function<12.99386804/2 in :erl_eval.expr/5>
iex(4)> f.(:me, me: :me)             
:me
iex(5)> f.(:me, mee: :me)
** (FunctionClauseError)

However we cannot match on keys in Maps:

iex(5)> f = fn(a, %{a => a}) -> a end  
** (CompileError) iex:5: illegal use of variable a inside map key match, maps can only match on existing variables by using ^a
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (stdlib) lists.erl:1355: :lists.mapfoldl/3 
  1. Is there a way to match on keys in Maps?
  2. Why can we do it with Keywords but not with Maps?
1 Like

You can use the pin operator ^ for this.

a = 1
case %{1 => :value} do
  %{^a => value} -> value
end

So f = fn(a, %{a => a}) -> a end might be “rewritten” as

f = fn a, map -> case map do %{^a => ^a} -> a end end
iex(2)> f.(1, %{1 => 1})
1

Note, however, you’d need to add a case which would handle unmatched maps as well.

2 Likes

Sure. Impossible to match on the parameters, then.

2 Likes

It’s currently impossible to match on “variable” map keys when passed as function arguments, yes.
It is possible to match on map values for predefined keys

def f(%{a: a}, %{a: a}), do: true
def f(_m1, _m2), do: false
1 Like