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
Most Liked
ibgib
I like OK.with because
- It let’s regular elixir users immediately grok that it’s like
with. - It’s idiomatic English! “I’m OK with that.”
(Try sounds like a try/catch/rescue replacement as opposed to a with replacement)
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 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
Version 1.10.0: Add map/2 and ~> to treat result tuples as Functors.
The latest version of ok (1.10.0) has been release. It adds functionality to transform a value within an :ok tuple. e.g.
Examples
iex> {:ok, 5} ~> Integer.to_string
{:ok, "5"}
iex> {:error, :zero_division_error} ~> Integer.to_string
{:error, :zero_division_error}
iex> {:ok, "a,b"} ~> String.split(",")
{:ok, ["a", "b"]}
Crowdhailer
Version 1.6.0 released, with two main improvements.
-
Much better errors, showing code snippet, expected and actual values
Binding to variable failed, '{:bad, 6}' is not a result tuple. Code b <- bar(a) Expected signature bar(a) :: {:ok, b} | {:error, reason} Actual values bar(a) :: {:bad, 6} -
required/2to turn nilable values to result tuplesmaybe_port = Map.get(config, :port) {:ok, port} | {:error, :port_number_required} = OK.required(maybe_port, :port_number_required)
vic
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 likeOkand also allowed you to define other patterns besides:ok/:errortagged tuples.
See thedefpipeon 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/: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 awithmade 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 for OK.with since it’s closer to what with does and when reminds me of guards.








