Now I can’t be sure but consider this:
iex(1)> (a_and_b = %{a: "A"}) = %{a: "A", b: "B"}
%{a: "A", b: "B"}
iex(2)> a_and_b
%{a: "A", b: "B"}
iex(3)> fun = fn(a_and_b = %{a: "A"}) -> a_and_b end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(4)> fun.(%{a: "A", b: "B"})
%{a: "A", b: "B"}
iex(5)>
Given the observed behaviour it seems that adding the parentheses isn’t overriding the right associativity of the match operator but instead is turning (a_and_b = %{a: "A"})
into a full pattern, so:
-
%{a: "A", b: "B"}
is still first matched to %{a: "A"}
- and once successful, the result is bound to
a_and_b
.
iex(5)> (a_and_b = %{a: "A"}) = %{a: "A", b: "C"}
%{a: "A", b: "C"}
iex(6)> a_and_b
%{a: "A", b: "C"}
iex(7)> (a_and_b = %{a: "A"}) = %{a: "B", b: "C"}
** (MatchError) no match of right hand side value: %{a: "B", b: "C"}
iex(7)> a_and_b
%{a: "A", b: "C"}
iex(8)>
Unfortunately the AST simply reflects the syntax (i.e. the pattern(s) aren’t identified at this point)
iex(8)> ast = quote do: a_and_b = %{a: "A"} = %{a: "A", b: "B"}
{:=, [],
[
{:a_and_b, [], Elixir},
{:=, [], [{:%{}, [], [a: "A"]}, {:%{}, [], [a: "A", b: "B"]}]}
]}
iex(9)> ast = quote do: (a_and_b = %{a: "A"}) = %{a: "A", b: "B"}
{:=, [],
[
{:=, [], [{:a_and_b, [], Elixir}, {:%{}, [], [a: "A"]}]},
{:%{}, [], [a: "A", b: "B"]}
]}
iex(10)>
Another experiment:
% file: demo.erl
-module(demo).
-export([demo1/0,demo2/0]).
demo1() ->
A_and_B = #{a := "A"} = #{a => "A", b => "B"},
A_and_B.
demo2() ->
(A_and_B = #{a := "A"}) = #{a => "A", b => "B"},
A_and_B.
1> c("demo.erl").
{ok,demo}
2> demo:demo1().
#{a => "A",b => "B"}
3> demo:demo2().
#{a => "A",b => "B"}
4> c("demo.erl",'S').
** Warning: No object file created - nothing loaded **
ok
5>
File: demo.S
{module, demo}. %% version = 0
{exports, [{demo1,0},{demo2,0},{module_info,0},{module_info,1}]}.
{attributes, []}.
{labels, 11}.
{function, demo1, 0, 2}.
{label,1}.
{line,[{location,"demo.erl",5}]}.
{func_info,{atom,demo},{atom,demo1},0}.
{label,2}.
{move,{literal,#{a => "A",b => "B"}},{x,0}}.
{get_map_elements,{f,3},{x,0},{list,[{atom,a},{x,1}]}}.
{test,is_eq_exact,{f,3},[{x,1},{literal,"A"}]}.
return.
{label,3}.
{line,[{location,"demo.erl",6}]}.
{badmatch,{x,0}}.
{function, demo2, 0, 5}.
{label,4}.
{line,[{location,"demo.erl",9}]}.
{func_info,{atom,demo},{atom,demo2},0}.
{label,5}.
{move,{literal,#{a => "A",b => "B"}},{x,0}}.
{get_map_elements,{f,6},{x,0},{list,[{atom,a},{x,1}]}}.
{test,is_eq_exact,{f,6},[{x,1},{literal,"A"}]}.
return.
{label,6}.
{line,[{location,"demo.erl",10}]}.
{badmatch,{x,0}}.
{function, module_info, 0, 8}.
{label,7}.
{line,[]}.
{func_info,{atom,demo},{atom,module_info},0}.
{label,8}.
{move,{atom,demo},{x,0}}.
{line,[]}.
{call_ext_only,1,{extfunc,erlang,get_module_info,1}}.
{function, module_info, 1, 10}.
{label,9}.
{line,[]}.
{func_info,{atom,demo},{atom,module_info},1}.
{label,10}.
{move,{x,0},{x,1}}.
{move,{atom,demo},{x,0}}.
{line,[]}.
{call_ext_only,2,{extfunc,erlang,get_module_info,2}}.
Doesn’t look like those parens had any impact on demo2
whatsoever (it looks like A_and_B
was optimized right out because it wasn’t doing anything anyway).
Finally:
Match Operator = in Patterns
If Pattern1
and Pattern2
are valid patterns, then the following is also a valid pattern:
Pattern1 = Pattern2
When matched against a term, both Pattern1
and Pattern2
will be matched against the term. The idea behind this feature is to avoid reconstruction of terms.
So in your example
a_and_b = %{a: "A"} = %{a: "A", b: "B"}
can be abstracted as
Pattern2 = Pattern1 = Term
while
(a_and_b = %{a: "A"}) = %{a: "A", b: "B"}
can be abstracted as
(Pattern2 = Pattern1) = Term
according to the above rule both Pattern2
and Pattern1
will be matched against Term
(whether there are parens or not).
Note that in Erlang there is a clear differentiation between a map pattern, e.g. #{a := "A"}
versus a map as a term , e.g. #{a => "A", b => "B"}
; in Elixir they look the same, e.g. %{a: "A"}
and %{a: "A", b: "B"}
so that may make matters a bit more confusing.