fireproofsocks

fireproofsocks

This is more of a general question, but I’m wondering how other people in the community think about the pattern matching in function signatures.

Pattern matching is fairly straight-forward when you match on simple values, e.g.
def something([]), do: "Emtpy!"

I start to get some metal friction when I look at type-hinting when there are functions like this:

def something(c = %Plug.Conn{}), do: "Something with the conn"

At first glance, I would think that c contains the empty struct, but of course, it will have the FULL value of whatever was passed to the function so long as the input was of the proper type. In other words, it’s not really a pattern-match at all, it’s a type hint.

Granted, my confusion here is probably the baggage of seeing that syntax used not for type-hinting, but for supplying a default value in many other languages (e.g. PHP, Ruby, Python).

The pattern matching/type-hinting gets a bit stranger for me when it gets nested inside tuples. Consider the following example:

my_tuple = MyContext.get_resource_as_tuple()

case my_tuple do
  {:ok, resource = %{status_id: "valid"}} -> result
  {:ok, %{status: status}} -> "Boo. Status #{status} is not valid."
  {:error, msg}  -> "Error: #{msg}"
end

Again, the resource = %{status_id: "valid"} looks more like an assignment, and I have to remind myself how it actually works. Go, for example, omits the equals sign and puts the type after the variable when it is used as part of a type-check. PHP, puts the variable type in front of the variable when it’s used as part of a type-check.

How do others think about this when they’re walking through code?

Showing Posts 1 to 10

kokolegorille

kokolegorille

I use the other way around…

{:ok, %{status_id: "valid"} = resource}

It reminds me of JS destructuring and got used to this form, and it does not look like an assignement.

dimitarvp

dimitarvp

I just had to get used to it. As @kokolegorille mentioned, this is a destructuring statement; the assignments inside the pattern are giving you partial matches on the exact piece of the data inside the bigger piece of data.

IMO Erlang/Elixir pattern matching isn’t type checking. It’s more like asserting the shape of the data itself. Any type checking along the way is a nice bonus.

(As an example, you can use pattern matching with map syntax in your function head and it will happily accept both a map and a struct, if the struct has the exact same keys that your pattern matching expression requires.)

kokolegorille

kokolegorille

It is destructuring in JS, but pattern match in Elixir :slight_smile:

axelson

axelson

Scenic Core Team

Also the reason that %{} matches any map is that otherwise pattern matching on maps wouldn’t be very useful because you would never be able to do a partial match, you’d always have to define all the keys even if you aren’t interested in them. i.e. this would give a MatchError:

%{result: result} = %{result: 42, errors: []}
stefanchrobot

stefanchrobot

Actually, it is a pattern match, because if you try to pass a plain map it will fail with match error. If you define a struct, you can match on struct type or you can work with any maps:

defmodule User do
  defstruct [:id, :name]

  # more (runtime) type-checking and safety
  def say_hello(%User{name: name}), do: "my name is #{name}"

  # works with any map that has the :name key
  # more extensible, but then - should this function live here or elsewhere?
  def say_hello(%{name: name}), do: "my name is #{name}"
end

So it’s really up to you to decide which approach works best for what you’re trying to achieve.

As a side note, even though def something(c = %Plug.Conn{}) and def something(%Plug.Conn{} = c) are technically the same, I always strongly push for the latter, since it’s more intuitive and more in line with pattern matching inside of a function (from right to left):

def some_func(...) do
  %Plug.Conn{} = c  # pattern match c: it has to be a Plug.Conn
  c = %Plug.Conn{}  # rebinding c to empty Plug.Conn
end
rvirding

rvirding

Creator of Erlang

I quite agree with the opinion that writing %Plug.Conn{} = c feels much better in a pattern match, it is how you would write the match in code. Though some prefer the other way as they see it as first matching then binding the variable. But they are wrong. :wink:

Also I just want to point out that you can use the = alias in any patterns anywhere so you can write patterns like {a, b, c} = t and [%Plug.Conn{} = c | rest]. You can have your cake and eat it,

I do just want to stress that both ways result in the same code so there is no “better” choice wrt efficiency.

11
Post #6
Qqwy

Qqwy

TypeCheck Core Team

As a side note: Elixir has a syntax for default values to functions as well, it’s \\:

def foo(required, optional \\ 42) do
  IO.inspect({required, optional})
end
tcoopman

tcoopman

Something that I found a bit confusing in the beginning was that %{} matches any map, but [] matches an empty list.

NobbZ

NobbZ

Its even worse once you use them in types vs match…

[foo] in a match means a list with exactly one element, in a type though it means a list of items of type foo, this list can be empty or have arbitrary many elements.

%{} in a pattern match means any map, empty or not, as a type though it means the empty map, literally.

I got used to it, but still sometimes fall into this pit…

OvermindDL1

OvermindDL1

That’s because lists have a construct to match non-empty lists, that being [_|_], there is no such syntax for maps, though if there were then I could see it operating like lists, to borrow from another language perhaps something like %{_ => _}, however matching purely empty maps is an extremely rare case, if ever, popping up in Elixir, so using %{} for that seems useful, unlike lists where matching the empty list is extremely common. :slight_smile:

Where Next? Top

Trending in Discussions Top

cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
515 12931 101
New
wmnnd
Hey folks, I wanted to share with you a project I’ve been working on for a while: It’s an Open Source (AGPLv3) newsletter tool. Think Ma...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
sntran
In my third year of college, around 2010, I found Erlang. A few years later, I found Elixir. I liked the ideas at once: many small proces...
New
Ganna
Hi everyone, I have 8 years of production experience with Erlang/OTP and I’m just starting to get to know Elixir. What would you learn f...
New
gtcode
I’ve been tinkering with Lynx (by José) via hold_ledger. I used AI to write the code, a separate specification, the tests, and the Lean p...
New
AstonJ
A remark @Garrison made… ..has already inspired a thread for the technical side of reclaiming our internet, but there is of course anot...
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
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
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
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

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews