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?
Trending in Discussions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixir-ls
- #ai
- #elixirconf-us
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
kokolegorille
I use the other way around…
It reminds me of JS destructuring and got used to this form, and it does not look like an assignement.
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
It is destructuring in JS, but pattern match in Elixir
axelson
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 aMatchError: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:
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{})anddef 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):rvirding
I quite agree with the opinion that writing
%Plug.Conn{} = cfeels 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.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} = tand[%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.
Qqwy
As a side note: Elixir has a syntax for default values to functions as well, it’s
\\:tcoopman
Something that I found a bit confusing in the beginning was that
%{}matches any map, but[]matches an empty list.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 typefoo, 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
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.