d4mr

d4mr

If structs are just maps underneath then how does pattern matching to ensure struct type work?

For example:

defmodule Attendee do
    defstruct name: "", paid: false, over_18: true

    def may_attend_after_party(attendee = %Attendee{}) do
        attendee.paid && attendee.over_18
    end

    def may_attend_after_party_without_struct(attendee = %{name: "", paid: false, over_18: true}) do
        attendee.paid && attendee.over_18
    end
end
iex(1)> Attendee.may_attend_after_party(%Attendee{name: "John"})
false
iex(2)> Attendee.may_attend_after_party(%Attendee{name: "John", paid: true})
true
iex(3)> Attendee.may_attend_after_party_without_struct(%Attendee{name: "John", paid: true})
** (FunctionClauseError) no function clause matching in Attendee.may_attend_after_party_without_struct/1    
    
    The following arguments were given to Attendee.may_attend_after_party_without_struct/1:
    
        # 1
        %Attendee{name: "John", over_18: true, paid: true}
    
    structwtf.exs:8: Attendee.may_attend_after_party_without_struct/1
iex(4)>

%Attendee{} evaluates to %Attendee{name: "", over_18: true, paid: false}, and if structs are just maps underneath, then shouldn’t the behaviour remain similar?

Showing Posts 1 to 8

al2o3cr

al2o3cr

Your example code omits the definition of Attendee.may_attend_after_party_without_struct/1; I tried def may_attend_after_party_without_struct(attendee = %{}) do and it worked, so you’ll need to provide more information about what behavior you’re expecting.

d4mr

d4mr OP

My bad, updated the example

ityonemo

ityonemo

Structs have a magic hidden key __struct__ which gets implicitly matched on when you prepend the struct module in between % and {. The default key/values are not filled in for matches, just the struct key.

Eiji

Eiji

In fact they are! :smiling_imp:

iex> Map.put(%{}, :__struct__, Attendee)       
%{__struct__: Attendee}

iex> %Attendee{}
%Attendee{name: "", over_18: true, paid: false}

What happen here? First of all the struct is not valid only because “some map” have __struct__ key even if value is valid. The map needs to have all keys available for such struct. The rest is sugar when inspecting.

The %{} syntax or more precisely % special form is just handled differently. Just to imagine:

defmodule Example do
  def add(a, b), do: a + b
  def add_alt(a, b), do: {:ok, add(a, b)}
end

The only difference between add/2 and add_alt/2 is that first returns just the result and second wraps it in “ok-tuple” which is useful for error handling.

If you check how it’s AST you would see that’s almost the same as in my example:

iex> quote do
  %Attendee{name: "", over_18: true, paid: false}
end
{:%, [],
 [
   {:__aliases__, [alias: false], [:Attendee]},
   {:%{}, [], [name: "", over_18: true, paid: false]}
 ]}

So inside % special form simple map (%{}) is used. The special form simply adds to your map a struct keys with their default values or nil if default value is not specified.

For more information see: %/2 special form

IloSophiep

IloSophiep

I’m not certain, but i think the way you pattern match in your function “without struct” is not what you intend to do. What you have is:

def may_attend_after_party_without_struct(attendee = %{name: "", paid: false, over_18: true}) do

That means only maps are “matched”, that

  • have an empty name
  • did not pay
  • and are over 18

I think what you actually want is just matching the existence of the keys, as in the following

def may_attend_after_party_without_struct(attendee = %{name: _, paid: _, over_18: _}) do
  attendee.paid && attendee.over_18
end

See how i tell the function head that i want the map to contain those three keys (:name, :paid and :over_18), but i discard the value of those keys - that way i “allow” any value.

Is this what you were hoping for?

d4mr

d4mr OP

I understand your pattern matching snippet, and in fact that is what I would expect to work.

However, because structs are matched in this manner:

def may_attend_after_party(attendee = %Attendee{}) do
    attendee.paid && attendee.over_18
end

and because %Attendee{} evaluates to %Attendee{name: "", over_18: true, paid: false} I expected the other behaviour to work.

However @ityonemo has given a very concise answer about why this works and @Eiji has provided an amazing demonstration of the fact.

Thanks a lot! This was my first interaction with the Elixir community, and makes me very happy to be a part of it :smile:

IloSophiep

IloSophiep

Do you mean those are the default values, if you create an empty Attendee struct? Because as with maps - at least i think - the pattern matching for “apparently empty stuff” is maybe not instantly obvious:

iex(1)> map = %{} = %{a: "foo", b: "bar"}
%{a: "foo", b: "bar"}
iex(2)> map
%{a: "foo", b: "bar"}

As you can see, the “empty map” %{} doe not only match empty maps, but any map. That tends to be very useful for the typical use. But that also means your usage of

def may_attend_after_party(attendee = %Attendee{}) do

does not mean you are trying to match an empty attendee or a default attendee. It means you are matching any attendee struct, no matter the contents.

I hope that was clear - does it make sense to you, the way i described it?

al2o3cr

al2o3cr

“Evaluates to” is the gotcha here, the arguments of a function are always a “match context” which has special properties - this:

def some_fun(whole_arg = %{single_value: v}) do

and this:

def some_fun(%{single_value: v} = whole_arg) do

both result in the same function that matches a map with a single_value key and binds whole_arg and v.

This is not the same = that you get when writing those statements alone:

# requires `v` to be defined beforehand, binds a new map `whole_arg`
# always succeeds
whole_arg = %{single_value: v}

# vs

# requires `whole_arg` to be defined beforehand as a map, binds `v` 
# fails with MatchError if `whole_arg` does not have a :single_value key
%{single_value: v} = whole_arg

I suspect this is why it’s a common convention to write pattern-matches on structs on the left-hand side:

def may_attend_after_party(%Attendee{} = attendee) do

as it’s clearly distinguishable from the “evaluation” =.

— All posts loaded —

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
water
I’m aware of 2 such projects, are there others? git: GitHub - team-alembic/realworld: A fullstack Phoenix LiveView application with back...
New
itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
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
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
csmr
Hi all, I’m new here and would like to hear peoples views on my Elixir/Phoenix based game-engine! :smiley: Mutonex gamedesign was finish...
New
Stef-Verniers
Hello I’ve been coding in Elixir for almost two years now because of my current job where we implemented Elixir in our application. But ...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
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
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
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews