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

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 92995 915
New
AstonJ
The obligatory hello world thread! Who are you and where are you from? :stuck_out_tongue:
4616 55835 594
New
caslu
I want to open this thread for you all to discuss and help those who really like Ash but are still hesitant to use it in a real project. ...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
Herve37
We’re evaluating API mocking tools for OpenAPI-based projects and would love to hear what other teams are using. We’re particularly inte...
New
GES233
I’m posting this in response to Jose’s recent tweet (Cr. link) : People are sleeping on Elixir for a coding harness: Hot-code swappi...
New
_mfierro
Hello, I wrote Stop My Hand, a Scattergories-like web application using Phoenix/LiveView as my learning project for Elixir (after readin...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & 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
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New
webofbits
Aludel - LLM Evaluation Workbench Aludel is an embeddable Phoenix LiveView dashboard for evaluating and comparing LLM prompts across mult...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews