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 94592 917
New
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...
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
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
axelson
Hi there! :wave: @frigidcode and I (but mostly him) have been running an Elixir Book club, we’re almost done with Designing Elixir Syste...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
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
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
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
georgeguimaraes
Just published claude-code-elixir, a plugin marketplace for Claude Code with Elixir support. These are the plugins I’ve been using for my...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews