Crowdhailer
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
Trending in Announcing
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
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #performance










First 10 of 31 Posts
NobbZ
Your syntax above has the same semantics as haskells
do-notation in monads. This special case comes very close to theMaybe-implementation of a Monad and would look roughly like this (assuming magical IO, just working everywhere):Therefore I’d go in fact for that and name that macro
monadic. Your block would read as this: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
forimplies some sort of "loop"ing, since it is heavily known from a plentora of imperative languages.ibgib
I like
OK.withbecausewith.(Try sounds like a
try/catch/rescuereplacement as opposed to awithreplacement)chrismccord
This is a great lib for playing with macros! That said, I’m personally
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
Okis 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:oktuple. Usingwith, you simply add a new clause, withOkyou need to rewrite the entire block.Crowdhailer
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.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
:errororfalseornilor{:error, too, many, reasonsCrowdhailer
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”
Note there is no else clause support, Yet.
ibgib
Exactly. Less noise, less typing, more descriptive. (I use
witha lot, so this would be a big win for me - but definitely would needelsematching.)Yes, I think
OK.whenreally 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 thanOK.withfor that reason, but it would separate itself further from the standardwithand might require more mental translation for new readers. On the whole though, I personally lean towardsOK.whennow.vic
Searching
happy pathbrought 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 toOkor explore something along the lines.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
Okand also allowed you to define other patterns besides:ok/:errortagged tuples.See the
defpipeon the README, for examplehappy
Then for cases that were code was not that homogeneous (not always returning
:ok/:errortagged tuples) I just wanted to avoid lots of nested case and ended up doing something along the lines ofwith(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
withmade for pipes. That is, you could specify afoo() |> pit(value <- pattern) |> bar()and letbar()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.BTW, great work on
Ok@Crowdhailer, I’d go forOK.withsince it’s closer to whatwithdoes andwhenreminds me of guards.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
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
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
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.