# Reference material on pattern matching precedence order?

Hey guys, anyone has any references/docs about pattern matching precedence order?

I mean

``````iex> a_and_b = %{a: "A"} = %{a: "A", b: "B"}
> %{a: "A", b: "B"}
``````

is done right to left, right?

So

``````iex> a_and_b
> %{a: "A", b: "B"}
``````

But what if I wanted to do

``````iex> (a_and_b = %{a: "A"}) = %{a: "A", b: "B"}  # note the parens
> %{a: "A", b: "B"}
``````

For me it evaluates the same, as if there weren’t any parens…

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}]}.

{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}]}.

{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).

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.

``````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.

4 Likes

It makes sense. Thank you for your help!

Thus:

``````iex(16)> %{c: "C"} = %{a: "A"} = %{a: "A", b: "B", c: "C"}
%{a: "A", b: "B", c: "C"}

iex(17)> (%{c: "C"} = %{a: "A"}) = %{a: "A", b: "B", c: "C"}
%{a: "A", b: "B", c: "C"}

iex(18)> (%{c: c} = %{a: a}) = %{a: "A", b: "B", c: "C"}
%{a: "A", b: "B", c: "C"}
iex(19)> c
"C"
iex(20)> a
"A"
``````

Saying “both `Pattern2` and `Pattern1` will be matched against `Term` (whether there are parens or not)” is functionally equivalent to saying “the parens are ignored, the associativity is always from right”. For `Pattern2` will be matched against `Term`, resulting in `Term`. Then `Pattern1` will be matched against the result, that is `Term` again.

both `Pattern1 and Pattern2` will be matched against the term.

to me it implies in

``````Pattern0 = ... = PatternN = Term
``````

`Term` is matched against all `{Pattern0,..,PatternN}`, i.e. between all the patterns no associativity of any kind comes into play - each pattern is matched against the term individually and if any one of them fails the whole match fails.

2 Likes

Yes, this is how it works. It means that each pattern will be matched against the term. There is an implicit grouping (Pattern0 = (Pattern2 = … (PatternN = Term)…)) so if variables occur in many the binding behaves as the matching goes from the inside out. Fro example:

``````iex(1)> {a,b} = {b,a} = {3,4}
{3, 4}
iex(2)> a
3
iex(3)> b
4
iex(4)> {b,a} = {a,b} = {3,4}
{3, 4}
iex(5)> a
4
iex(6)> b
3
``````

If you pin (’^’ ) a variable in any of the patterns then this importing of the value occurs before any matching:

``````iex(7)> {^x,y} = {y,x} = {3,4}
** (CompileError) iex:7: unbound variable ^x
(stdlib) lists.erl:1354: :lists.mapfoldl/3
iex(7)> {x,y} = {y,^x} = {3,4}
** (CompileError) iex:7: unbound variable ^x
(stdlib) lists.erl:1354: :lists.mapfoldl/3
(stdlib) lists.erl:1355: :lists.mapfoldl/3
``````

(Erlang does not have this “issue” of ordering as variables can’t be rebound.)

6 Likes

Thanks for the clarification.

It raised one more question, though.

Interestingly, playing with your examples shows the parenthesis do make a difference:

``````iex(1)> {a,b} = {b,a} = {3,4}
{3, 4}
iex(2)> a
3
iex(3)> b
4
``````

but

``````iex(1)> ({a,b} = {b,a}) = {3,4}
** (MatchError) no match of right hand side value: {3, 4}
``````

So in the last example seems `{a,b} = {b,a}` is evaluated first, like `((Pattern0 = Pattern1) = Term)`

Yes, it seems like the RHS of a `=` expression must be a term. Now the value of a `=` pattern match is the RHS so it works going right-to-left. Pinning is done first.

Hmm …

``````iex(1)> ({a,b} = {b,a}) = {3,4}
** (MatchError) no match of right hand side value: {3, 4}

iex(1)> ({a,b} = {b,a}) = {3,3}
{3, 3}
iex(2)> {b,a} = ({a,b} = {a,c}) = {3,4}
{3, 4}
iex(3)> a
4
iex(4)> b
3
iex(5)> ({b,a} = {a,b}) = {a,c} = {3,4}
** (MatchError) no match of right hand side value: {3, 4}

iex(5)> ({a,b} = {a,c}) = {b,a} = {3,4}
{3, 4}
iex(6)> a
3
iex(7)> b
4
iex(8)>
``````

Pinning is done first.

So essentially the parentheses are disallowing the re-binding that would happen in their absence.

The parentheses are effectively pinning all bindings to their initial value (inside the parentheses). I’m simply re-emphasizing this because up to now I’ve only encountered the pinning concept in connection with `Kernel.^/1` - this is the first time that I’ve seen parentheses having a similar effect.

The order of evaluation (right-to-left) seems unaffected.

If anything the parentheses around the patterns seem to be “grouping patterns” that have to succeed together while single assignment is in effect for all bindings inside the grouping (though names already bound outside of the grouping seem to get a single re-assignment inside the grouping).

2 Likes

Oooo, nice find on the work-around for fixing Elixir’s inconsistency with erlang matchers here!

1 Like