Crowdhailer

Crowdhailer

Creator of Raxx

Semantic pattern matching, useful or not?

It is rare to use direct calls to send in elixir. The call is normally wrapped in a function which is responsible for sending the correct message. The most common example of this has to be GenServer.call. This is seen as good practice as it allows the structure of the message to be hidden. The structure of the message being an implementation detail only.

However when receiving a message or pattern matching the internal details must always be known. At least this is certainly true in erlang but does not need to be true in Elixir because it has macros.

The most simple case I can think of is the convention of using {:ok, value} || {:error, reason}. This is an implementation of how a function can indicate it has succeeded or failed. It would be possible to use another convention for example %Try.Success{value: value} || %Try.Failure{reason: reason}. To hide which implementation is being used macros can be employed. E.g in a case statement. (The same is true in a receive block).

For example.

case my_func() do
  {:ok, value} ->
    # continue
  {:error, reason} ->
    # report error
end

can be replaced with what I call “semantic pattern matching”

case my_func() do
  success(value) ->
    # continue
  failure(reason) ->
    # report error
end

An implementation of the success and fail macros is available in my OK project.

I will admit that the extra value in this case might not be very high. The example of :ok/:error tuples is both simple and common knowledge. However I think it could be very useful in a few cases, for example.

  • When a process could receive messages from two different libraries. e.g. a Gen server that is running taskes. It might receive a call message or a message notifying of a completed task.
  • A finite statemachine that is receiving commands to change state.

First Post!

sztosz

sztosz

I’m not sure I follow. Is there more to this than changing {:ok, value} -> into success(value) -> and {:error, reason} -> to failure(reason) -> ?

I mean, does the success and failure functions/macros do anything more than pattern match inside them to value and reason? What If i want to pattern match to simple :error or {:error, reason, metadata} or something similar?

To be perfectly honest I don’t see any value added here :frowning: To me it neither looks more readable, even in example above :ok and :error have distinct color, so it’s easier to catch them in the code, nor give any usable abstraction or “shortcut” to make development cycle shorter.

But as I stated at the beginning I might simply not understand the purpose fully ¯\_(ツ)_/¯

Most Liked

josevalim

josevalim

Creator of Elixir

For what is worth, this is a question I frequently ask myself: if it is worth adding abstractions on top of pattern matching and provide conveniences such as defpattern and/or defguard? It is one of those things I am somewhat glad the Erlang VM restrict us from generalizing because we would probably have done so some time ago and the solution may not have been ideal.

Maybe one day we will add something that resembles discriminated unions and named patterns, which would give the features you described above, but right now there are still many questions around on how to generalize such patterns while providing good performance and sane semantics. The current mechanism, although it may be repetitive some times, it is simple, explicit and straight-forward. See the expat thread for a similar discussion.

11
Post #3
X4lldux

X4lldux

I’ve created a library for doing exactly this, called disc_union. For your example it would look like this:

defmodule Result do
  use DiscUnion

  defunion :ok in any() | :error in atom
end

defmodule Example do
   use Result

   def foo() do
     Result.case my_func() do # expects a %Result{} struct
       :ok in value -> #continues
       :error in reason -> #report the error
     end
   end
end

NOTE: you can also “import” a regular error tuple into the Result struct using Result.from!/1 function and it will check if what your are importing was defined as one of the variants.

Downside is that for each discriminated union there has to be a specially created case macro, in order for it to “know” what valid variants are in this union and warn you (at compile time) when you miss one or implement it with a wrong shape. I find it very useful and use it in production even though I still consider this a research project.

Qqwy

Qqwy

TypeCheck Core Team

I believe that if your patterns become too complex in a single function, then that might be a very clear indicator that that function could be split in smaller parts.

I do like pattern-matching extensively to ensure that people know it right away when they put garbage in my libraries’ functions. Sometimes these matches become relatively long. I have been tempted to fiddle with pattern-providing macros before, but have not gone through with it until now because I also believed that this would make it harder to read what was going on. I think there are some use cases where you frequently want to match a struct that is in a certain state.

I agree with @josevalim and @crowdhailer: This is not something Elixir itself should provide, but it is great that we can, if it turns out to be useful for the project at hand, write macros that do this stuff.

Last Post!

fluffynukeit

fluffynukeit

Hi, I found this discussion thread after asking a similar question on reddit, linked here: https://www.reddit.com/r/elixir/comments/171kejc/how_to_reconcile_module_api_with_pattern_matching/

I, too, am interested in what is being referred to as "semantic patterns.’ I think it is worthwhile to pattern match on properties of the data instead of directly on the internal representation of data. There are use cases in which you want the internal representation to be hidden from the library user, but doing so disallows pattern matching in function heads, which is an essential part of using GenServer handle_* callbacks.

Where Next?

Popular in Discussions Top

AstonJ
Are there any Elixir or Erlang libraries that help with this? I’ve been thinking how streaming services like twitch have exploded recentl...
New
PragTob
Hey everyone, this has been brewing in my head some time and it came up again while reading Adopting Elixir. GenServers, supervisors et...
New
wmnnd
The Go vs Elixir thread got me thinking: Would it be too hard to implement a simple mechanism for creating Go-style static app binaries f...
New
chulkilee
Here are the list of HTTP client libraries/wrappers, and some thoughts on HTTP client in general. I’d like to hear from others how they w...
New
lucaong
Hello Elixir and Nerves community, I have been working for a while on an open-source embedded key-value database for Elixir, that I call...
230 14403 124
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
paulanthonywilson
I like Umbrella projects and pretty much always use them for personal Elixir stuff, especially Nerves things. But I don’t think this is ...
New

Other popular topics Top

baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 55125 245
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31586 112
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New

We're in Beta

About us Mission Statement