Parashoe

Parashoe

Understanding the match operator

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.

Marked As Solved

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:

https://www.erlang.org/doc/system/expressions.html#the-match-operator

Also Liked

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

derek-zhou

derek-zhou

Match is right associative.
So,

%{a: x} = %{b: y} = %{a: "what", b: "happened"}

is really:

%{a: x} = (%{b: y} = %{a: "what", b: "happened"})

Also, Map matched on partial keys, so, %{b: y} = %{a: "what", b: "happened"} is a match, resulting the full map.

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.

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.

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.

Where Next?

Popular in Questions Top

gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
jc00ke
Expanding on this topic: https://forum.elixirforum.net/t/map-typespec-question/19217 Let’s say I have a map with required and optional k...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

Other popular topics Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

We're in Beta

About us Mission Statement