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?

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.

Sure. Impossible to match on the parameters, then.

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