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.

First 10 of 16 Posts Switch mode

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.

Parashoe

Parashoe OP

Are you saying that x is assigned with "what" because the first match %{b: y} = %{a: "what", b: "happened"} includes a?

I’m testing out forcing the left side to evaluate first and it still seems to match. I also included a match with undefined variables first to see what the error is, and then after to see if the two independent maps match because they don’t have conflicting values.

iex(1)> %{a: x} = %{b: y}
error: undefined variable "y"
└─ iex:1

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

iex(1)> (%{a: x} = %{b: y}) = %{a: "what", b: "happened"}
%{a: "what", b: "happened"}
iex(2)> x
"what"
iex(3)> y
"happened"
iex(4)> %{a: x} = %{b: y}
** (MatchError) no match of right hand side value: %{b: "happened"}
    (stdlib 6.2) erl_eval.erl:667: :erl_eval.expr/6
    iex:4: (file)
iex(4)>

But it would match if there were partial keys matching?

I think I’m starting to get the semantics here: match(pattern, pattern) -> option<pattern> (pseudo-code types) and finally it builds until there are no match operators and you have one pattern that defines the undefined variables in it.

Apologies that my vocabulary is a bit imprecise here.

Parashoe

Parashoe OP

No I’m wrong. It is not commutative or associative. It’s RTL 100% like @derek-zhou says, but you have more freedom to re-arrange the order on the left hand side because of either some match semantics or map semantics I don’t fully understand.

Again I’m confused about what the section here: Functions and Pattern Matching is saying about pattern match independence:

If we switch the order of %{name: person_name} and person in the list, we will get the same result because each are matching to fred on their own.

We swap the variable and the map:

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

And call it with the same data we used in Greeter2.hello/1:

call with same old Fred

Greeter3.hello(fred)
"Hello, Fred"
%{age: "95", favorite_color: "Taupe", name: "Fred"}

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.

Parashoe

Parashoe OP

Is the article just wrong or poorly worded? person = %{name: person_name} seems to match person with the full map because %{name: person_name} = fred results in a match on the full map.

Parashoe

Parashoe OP

In regards to forcing the left side to evaluate first and having it succeed.

iex(1)> (%{a: x} = %{b: y}) = %{a: "what", b: "happened"}
%{a: "what", b: "happened"}
iex(2)> x
"what"
iex(3)> y
"happened"

It’s because %{a: x} = %{b: y} matches and leaves an undefined variable that when put in the larger expression is defined.

Parashoe

Parashoe OP

I stepped away to eat, and looking at this again with a clear head is mostly embarrassing. Nonetheless, there are some subtleties concerning variable assignment to clarify.

The match operator only assigns variables on the left side, but you can still match with undefined variables on the right if the match is part of a larger expression where they will be bound. This is why giving the left match precedence in (%{a: x} = %{b: y}) = %{a: "what", b: "happened"} works.

What initially threw me off from this article Functions and Pattern Matching still confuses me though. It states: “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,”. Is this incorrect? Are there special pattern match rules within function signatures? Isn’t it just the same as matching RTL starting with the argument on the right most side?

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.

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.

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

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?

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
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
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New

We're in Beta

About us Mission Statement