Crowdhailer

Crowdhailer

Creator of Raxx

OK - elegant error handling with result monads (alternative to Elixir `with` special form)

Experimenting with this code.

OK.try do
  user <- fetch_user(1)
  cart <- fetch_cart(1)
  order = checkout(cart, user)
  save_order(order)
end

Ok.with/1 supports an else block that can be used for handling error values.

OK.with do
  a <- safe_div(8, 2)
  _ <- safe_div(a, 0)
else
  :zero_division -> # matches on reason
    {:ok, :inf}     # must return a new success or failure
end

The cart example above is equivalent to

with {:ok, user} <- fetch_user(1),
     {:ok, cart} <- fetch_cart(1),
     order = checkout(cart, user),
     {:ok, order_id} <- save_order(order)
do
   {:ok, order_id}
end

I have an implementation marked as beta as part of my OK project.

The Elixir with keyword has been very helpful for handling code with lots of branches, normally many error conditions. Such as the example above.

In the with example I find it is strange that the majority of my code my code lives in a list of arguments. It starts to get very lumpy if there are receive blocks or anonymous functions to get these values. Also in 90% of cases I am matching on an :ok tuple so don’t want to repeat that.

For these reasons I have been using the alternative macro from OK. It is far more restrictive because it will only match on :ok/:error tuples. However I like the restriction because it means that no matter how complex the block it will also only return :ok/:error tuples.
I am not sure that try is the best name. Alternatives that I am considering are

  • when, seams so make sense linguistically but already a keyword in elixir
  • with, familiar to elixir users
  • for, monadic for comprehension which is what this is but that might not be a very accessible term.
  • try, As is, however it doesn’t catch errors so the name could be misleading

First 10 of 31 Posts Switch mode

NobbZ

NobbZ

Your syntax above has the same semantics as haskells do-notation in monads. This special case comes very close to the Maybe-implementation of a Monad and would look roughly like this (assuming magical IO, just working everywhere):

f = do
  user <- fetch_user 1
  cart <- fetch_cart 1
  let order = checkout cart user
  save_order order

Therefore I’d go in fact for that and name that macro monadic. Your block would read as this:

OK.monadic do
  ...
end

The alternative names you mentioned at the end of the post are all four already keywords, top-level-macros or specialforms and have some meaning asigned to them. especially for implies some sort of "loop"ing, since it is heavily known from a plentora of imperative languages.

ibgib

ibgib

I like OK.with because

  1. It let’s regular elixir users immediately grok that it’s like with.
  2. It’s idiomatic English! “I’m OK with that.”

(Try sounds like a try/catch/rescue replacement as opposed to a with replacement)

chrismccord

chrismccord

Creator of Phoenix

This is a great lib for playing with macros! That said, I’m personally :thumbsdown: on efforts around alternatives to with, as I haven’t seen justified benefits.

It’s worth pointing out that you should call out to functions in those cases. Also where you are seeing repetition, I am seeing explicit matching. The issues with Ok is that is hides the true match, and it gets more confusing if you were include a match within the 2nd elem, i.e. %{key: val} <- .... Is the rhs returning a tuple or map? It also breaks down the moment you want to match on something not in an :ok tuple. Using with, you simply add a new clause, with Ok you need to rewrite the entire block.

Crowdhailer

Crowdhailer OP

Creator of Raxx

By that argument I would definetly go with for. For comprehensions are extensible to other monadic types such as the result monad which is what this is. See details of Scala for comprehensions to see more about that.[quote=“chrismccord, post:4, topic:3264”]
The issues with Ok is that is hides the true match
[/quote]

That I consider a feature. The return type of functions used in this syntax must be a result tuple that is easy to describe. In many ways using the :ok tuple is just an implementation detail of a Result Monad. You could have an %OK{value: v}/%Error{reason: reason} struct instead and the behaviour would be the same. For that reason adding a pattern match on the left side is no more complicated.

%User{name: name} <- fetch_user(id)

The return value of fetch user is not ambiguous it is either a success with a user or an Error with single reason. Its far less ambiguous than with which can fail the match in any way. such as :error or false or nil or {:error, too, many, reasons

Crowdhailer

Crowdhailer OP

Creator of Raxx

I definitely think that going for sensible English is the best way to decide. However because the “do” comes before the main actions I think when might be better. Also I want to add an else clause

“I’m OK when doing x, y, z else log”

OK.when do
  user <- fetch_user(1)
  cart <- fetch_cart(1)
  order = checkout(cart, user)
  save_order(order)
else
  error ->
    Logger.warn(error)
end

Note there is no else clause support, Yet.

ibgib

ibgib

Exactly. Less noise, less typing, more descriptive. (I use with a lot, so this would be a big win for me - but definitely would need else matching.)

Yes, I think OK.when really captures the fact that it’s a tagged :okexpression and will return the {:ok, result} when the happy path is followed. So this is better than OK.with for that reason, but it would separate itself further from the standard with and might require more mental translation for new readers. On the whole though, I personally lean towards OK.when now.

vic

vic

Asdf Core Team

Searching happy path brought me here :D, turns out, I was experimenting with topics like this some time ago, actually some of my first macro libraries are either similar to Ok or explore something along the lines.

  • ok_jose
    This one was my first macro library, I just wanted a result monad, you know for piping ok tagged tuples. But one of my main focus was not to introduce (nor override) existing |> elixir operators.
    Mostly like Ok and also allowed you to define other patterns besides :ok/:error tagged tuples.
    See the defpipe on the README, for example
@doc "Pipes a valid changeset or its ok tagged tuple"
defpipe pipe_valid_changeset do
   valid = %Ecto.Changeset{valid?: true) -> valid
   {:ok, record} -> record
end

# then you can pipe functions that expect valid changesets
{:ok, %User{}}
|> cast(params, [:email, :password])
|> validate_password_conformation()
|> Repo.create
|> new_user_token()
|> pipe_valid_changeset() # no new syntax, just this guy rewrites the pipe
  • happy
    Then for cases that were code was not that homogeneous (not always returning :ok/:error tagged tuples) I just wanted to avoid lots of nested case and ended up doing something along the lines of with (back before it landed in Elixir 1.2, which I’ve been pretty much happy_with, except for its commas between match expressions)

  • pit
    This one is a bit weird (most things I do are), it was an experiment for having a with made for pipes. That is, you could specify a foo() |> pit(value <- pattern) |> bar() and let bar() take the value from the pattern matched result from foo.

Anyways, I guess it’s an interesting topic, and looks like for others too, since we have some people crafting this kind of things that do similar things (see the ones linked on Ok’s README), if you find any other similar, I’d be interested in looking at it. :slight_smile:

BTW, great work on Ok @Crowdhailer, I’d go for OK.with since it’s closer to what with does and when reminds me of guards.

ibgib

ibgib

That’s a great point. That would add interference with new users I think and would be confusing. “My vote” is back to OK.with.

Crowdhailer

Crowdhailer OP

Creator of Raxx

It’s certainly an interesting discussion.

I managed to have a very long discussion about it more than a year ago on the elixir google groups.

For my way of thinking I think I have found the best solution. However it’s interesting to see how people have different priorities. I was deliberately strict in what I was working with so I could be terse, however others were far more driven by flexibility

Crowdhailer

Crowdhailer OP

Creator of Raxx

So another argument for using with in this form I have just discovered is the following.

Standard elixir with won’t fail if you accidentally use = instead of <-.

I had the following code

with {:ok, user_id} = Map.fetch(body, "id"),
       {:ok, request_id} = Map.fetch(metadata, "request_id")
do
  # etc
else
  # handle
end

Both of those equals should have been <- but as I had not got a test for the failure case my mistake wasn’t spotted until later.

Where Next?

Trending in Announcing Top

bluzky
You may know https://ui.shadcn.com/, a UI component library for React. I really love it’s design style and components. I’ve built some co...
387 14960 120
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
shahryarjb
The Chelekom project is a library of Phoenix and LiveView components generated via Mix tasks to fit developer needs seamlessly. One of i...
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
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
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

Other Trending Topics Top

type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
bjorng
We want to introduce a new native datatype to Erlang: native records. Although replacing all tuple records with native records is not our...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New

We're in Beta

About us Mission Statement