Parashoe

Parashoe

I’m not fully understanding how matching works with function arguments. In Functions and Pattern Matching it is explained (IMO poorly) that everything in the argument is matched independently. As a result this experiment I made works:

iex(6)> test = fn (%{a: x} = %{b: y}) -> IO.puts "x:" <> x <> " y:" <> y end
#Function<42.18682967/1 in :erl_eval.expr/6>
iex(7)> test.(%{a: "what", b: "happened"})
x:what y:happened
:ok
iex(8)>

But I don’t fully understand the semantics of this. Is the = here the same match operator as usual? I tested the match operator alone for this behavior and it does seem to match everything!

iex(16)> %{a: x} = %{b: y} = %{a: "what", b: "happened"}
%{b: "happened", a: "what"}
iex(17)> x
"what"
iex(18)> y
"happened"
iex(19)>

So it seems that the match operator is both associative and commutative which I did not expect. I also assume passing arguments is the same as including another match with the passed argument. :white_check_mark: (this is correct)

Where is this documented? A quick search on the Elixir hexdocs of = and match aren’t fruitful. Pattern matching — Elixir v1.19.0-dev doesn’t say anything about it.

Edit: I think the article may just be wrong or poorly worded. I still need some clarification though

Thank you @LostKobrakai for clarifying things. The match operator always returns the right side value or fails with an error.

Showing Posts 16 to 7

Parashoe

Parashoe OP

I’m reading it now. I think this concurs with my reading of the Elixir School article and the match is distributed over the left compound pattern: The Match Operator and the Compound Pattern Operator “Since the construct inside the parentheses is a pattern, the = that separates the two patterns is the compound pattern operator (not the match operator). The match fails because the two sub patterns are matched at the same time”

Assuming the function argument pattern is compound, it actually is more like the former and the article has been right this whole time!
Let me double check with the example in Elixir:

iex(38)> m = %{key: :key2, key2: :value}
%{key: :key2, key2: :value}
iex(39)> %{k => :value} = %{key: k} = m
error: cannot use variable k as map key inside a pattern. Map keys in patterns can only be literals (such as atoms, strings, tuples, and the like) or an existing variable matched with the pin operator (such as ^some_var)
└─ iex:39

** (CompileError) cannot compile code (errors have been logged)

iex(39)> %{^k => :value} = %{key: k} = m
error: undefined variable ^k. No variable "k" has been defined before the current pattern
└─ iex:39

** (CompileError) cannot compile code (errors have been logged)

iex(39)> (%{^k => :value} = %{key: k}) = m
error: undefined variable ^k. No variable "k" has been defined before the current pattern
└─ iex:39

** (CompileError) cannot compile code (errors have been logged)

It seems Elixir prevents the edge case where the order of operation makes a difference by preventing the variable being used as a map key within the pattern. But I take it from here that the matches really are independent under the hood, and that this nuance almost never matters.

Thank you so much, this was really bugging me
Well onto lesson 7 :joy:

bjorng

bjorng

Erlang Core Team

A few years ago we in the compiler team at OTP started to get bug many bug reports about the Erlang compiler crashing when compiling some “strange” Erlang code. It turned out that the Erlang code that triggered the crashes was generated by a fuzzer, erlfuzz.

One of the classes of bugs in the Erlang compiler and in the documentation was in pattern matching. We had many discussions in the OTP team about how to best describe pattern matching and how to fix the compiler to be consistent and never crash.

The result was that we realized that there is not one match operator, but two distinct operators: the match operator and the compound match operator.

Here is the revised documentation about matching resulting from those bug reports and our discussions:

Parashoe

Parashoe OP

Okay I made a small module of two matches and got the lowered erlang code out:

defmodule MatchOperatorExamination do
  def left_precedence do
    (%{a: "foo", b: "bar"} = %{c: "qux"}) = %{a: "foo", b: "bar", c: "qux"}
  end

  def default_precedence do
    %{a: "foo", b: "bar"} = %{c: "qux"} = %{a: "foo", b: "bar", c: "qux"}
  end
end

Compiled and extracted using :beam_lib.chunks and then pretty printing following this guide: Disassemble Elixir code. And check if Erlang dead code… | by Gaspar Chilingarov | Learn Elixir | Medium

default_precedence() ->
    #{a := <<"foo">>, b := <<"bar">>} = #{c := <<"qux">>} =
                                            #{a => <<"foo">>, b => <<"bar">>,
                                              c => <<"qux">>}.

left_precedence() ->
    (#{a := <<"foo">>, b := <<"bar">>} = #{c :=
                                               <<"qux">>}) =
        #{a => <<"foo">>, b => <<"bar">>, c => <<"qux">>}.

By the time the expressions get to the BEAM they still have the parenthesis on the left hand side. So my question is as much an Erlang one as it is an Elixir one.

Erlang syntax does look Prolog-esque! especially with the dashes for declarations. I’ll be dogged if pattern matching is anything like horn clauses though. That said, Erlang pattern matching seems to have a little more than meets the eye given what we’re looking at here.

D4no0

D4no0

Erlang pattern matching is inspired by prolog. In prolog you can have bindings on both sides of match and it will try to solve it like an equation. I think erlang also supports a limited version of this behavior.

Parashoe

Parashoe OP

My intuition matches yours. Conceptually (%{a: x} = %{b: y}) is itself a pattern which intersects %{a: x} and %{a: y}. This pattern is then matched with the value on the right, and it’s probably implemented by transforming the order of execution as if it wasn’t there.

It’s just that this behavior is not specified or documented anywhere I can see. There are no other operators producing patterns for the left side like this right? This is a very special case?

mentero

mentero

I don’t think you can “give” precedence to the pattern match with ( ). I think they will just be ignored

In your example (%{a: x} = %{b: y}) = %{a: "what", b: "happened"} is a pattern match where the rightmost value is %{a: "what", b: "happened"}, so it has to be known and can’t bind variables, and the left side is (%{a: x} = %{b: y}) which is a pattern that the mentioned value has to satisfy. It means that the value need to satisfy both %{a: x} and %{b: y} and it will check against %{b: y} first before doing %{a: x}. That means that if the match fails on both patterns, the execution will stop and error on the b match first, but in order for match to succeed, all the patterns on the left side need to be matched.

So you are not comparing %{a: x} to %{b: y} - these are patterns for the same input. The only way when two patterns on the left side can interrupt each other is when they bind to the same variable, as there is a rule that value can be bind multiple times in one expression only if it binds to the same value. So %{a: x} = %{b: x} = %{a: "what", b: "happened"} will fail

EDIT:
Ok. No longer so sure enough about this statement to give tips on how it actually works. I simply always perceived match operator as folding from right to left, checking patterns in the process but it might be just my mental model

and it will check against %{b: y} first before doing %{a: x}. That means that if the match fails on both patterns, the execution will stop and error on the b match first, but in order for match to succeed, all the patterns on the left side need to be matched.

Parashoe

Parashoe OP

Thank you, this clarifies almost everything and explains why the article says that every pattern is matched independently with the argument. I appreciate the time you put into explaining this!


There remains a gap in my understanding. Giving precedence to %{a: x} = %{b: y} in (%{a: x} = %{b: y}) = %{a: "what", b: "happened"} is a successful match binding x to "what" and y to "happened". So the execution order does not appear to naively follow the order of the expression.

I’m missing something.

What are the semantics here?

LostKobrakai

LostKobrakai

I’d start from some underlying primitives here and build up understanding from there:

  • In elixir everything is an expression, which can be evaluated to a value – there are no statements or void return values.
    E.g. x = if 1 < 2, do: :a, else: :b will bind x with :a
  • The value returned by a match expression is the value of the right hand side value in the match expression.
    So for ^y = (pattern = y) the pattern = y will evaluate to the value of y
  • In the above the binding of pattern is kind of a side effect as it doesn’t affect the return value of the match expression.
    Therefore for the outer match expression ^y = (pattern = y) can be simplified to ^y = y
  • Being right associative match expressions can be chained without affecting the above properties even if you leave out explicit parenthesis. Each chained match expression will evaluate to the right most “input”, with all the individual match expressions having the chance to fail matching as well as resulting in variables being bound as side effects of their matching.
    a = %{b: 1} = %{d: d} = %{b: 1, d: 4} and the following function essentially the same.
%{d: d} = %{b: 1, d: 4}
%{b: 1} = %{b: 1, d: 4}
a = %{b: 1, d: 4}

These properties make it essentially irrelevant in which order patterns are placed in a chained set of match expressions, though only the patterns, the right most value needs to stay on the right. Yes technically there is an order – you could move least permissive match patterns more right to match earlier than less permissive ones – but in practise that’s really irrelevant.

Now finally moving to function parameters. Usually these cause confusion because the actually imporant “right most input” doesn’t actually show up when writing a function head.

Having a function shows just a chained set of match patterns.

def my_function(a = %{b: 1} = %{d: d}), do: […]

When calling my_function(%{b: 1, d: 4}) internally you’ll essentially get these patterns evaluated as: a = %{b: 1} = %{d: d} = %{b: 1, d: 4}, adding the provided parameters to the right most side of the chain.

Hence in function heads you can reorder all parts of a chained match expression given the input isn’t present explicitly and it wouldn’t functionally change the function (beyond microoptimizations).

Parashoe

Parashoe OP

Yes, the ultimate bindings are clear :+1: My problem is understanding the nuances.

The article says that reversing the match order:

defmodule Greeter3 do
  def hello(person = %{name: person_name}) do
    IO.puts "Hello, " <> person_name
    IO.inspect person
  end
end

results in a successful match with fred “because each are matching to fred on their own” and later says “Remember that even though it looks like %{name: person_name} = person is pattern-matching the %{name: person_name} against the person variable, they’re actually each pattern-matching to the passed-in argument”.

I read this to mean an expression like this: (person = fred) = (%{name: person_name} = fred) is happening opposed to: person = (%{name: person_name} = fred) although I think the latter is in fact correct.

I’m thinking the reason you can switch the order here is not because they are “matching to fred on their own” but because the %{name: person_name} = fred match, results in the whole map which can be bound to person to the left. Just like what happens with %{a: x} = %{b: y} = %{a: "what", b: "happened"}.

This is what I’m seeking clarity on and find poorly worded.

D4no0

D4no0

I think the article has pretty clear wording, for example if you have:

def hello(%{name: person_name} = person) do
    IO.puts "Hello, " <> person_name
    IO.inspect person
  end

The value person will have the entire map passed as the argument binded to it and person_name will bind the value of the key name. This kind of match also enforces the argument to be a map and contain the name key.

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
peck
Hi everyone. My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
bradley
I really like the adapter patterns that ecto, nebulex, waffle, etc. use and would love find something similar for a key management servic...
New
unaware8150
Hello folks! So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
Alvinkariuki
How Can I Optimise Compile Time Dependencies I have been building an elixir application for about 2 years now. Many modules and files ha...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews