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

To be honest I wish I could have used yield or return but out of the options I had the others all seamed worse. i.e. else and rescue.

Well, you ā€˜couldā€™. ^.^

OK.for do
  a <- safe_div(8, 2)
  b <- safe_div(a, 2)
  yield a + b
end

Youā€™d not be able to use a function named ā€˜yieldā€™ though, but I think thatā€™s fine.

Iā€™d still just vote for:

OK.for do
  a <- safe_div(8, 2)
  b <- safe_div(a, 2)
  a + b
end

It is perfectly and normally elixiry. :slight_smile:

1 Like

I certainly donā€™t disagree with you. Iā€™m going to use it in some code and see how I feel about it. Iā€™m also working on a writer monad for sending messages and am going to see how that looks with the separating after or not

I made a block syntax for monadā€™y style do expressions in expedeā€™s exceptional library as a PR if you want to see usage (though this one is for different style of error handling, the syntax is the same though): https://github.com/expede/exceptional/pull/18

1 Like

Version 1.9.0 released with new OK.try macro for normalising return values.

The syntax here is chosen to be basically the same as a natural try block but in this case without any errors being raised.

Example

require OK
import Raxx

OK.try do
  user <- fetch_user(1)             # `<-` operator means func returns {:ok, user}
  cart <- fetch_cart(1)             # `<-` again, {:ok, cart}
  order = checkout(cart, user)      # `=` allows pattern matching on non-tagged funcs
  saved_order <- save_order(order)
after
  response(:created)                # Value will be returned unwrapped
rescue
  :user_not_found ->
    response(:not_found)
  :could_not_save ->
    response(:internal_server_error)
end

This is a very nice way to write code that reliably returns the same type of value.
My favourite feature of this is it is familiar but raise can now be used for exceptions that should never happen.
i.e. programmer errors that should end up in bug tracking software as soon as possible.

With OK.for that was introduced in 1.8.0 I think that these are sane replacements for OK.with which had variable behaviour dependant on which code blocks had been defined.

2 Likes

Version 1.9.1 released to fix warnings.

An warning that could occur when using pipes ~>> has been rectified.

The following code now will not raise any errors.

{:ok, num}
~>> double()
3 Likes

Version 1.9.3 released to deprecate OK.with/1

The general purpose behaviour of OK.with has been replaced with two separate options

  • OK.for When a return value should be wrapped as an ok/error tuple
  • OK.try When error handling should be attempted, such as transforming errors to a 500 response.

See docs for details: https://hexdocs.pm/ok/1.9.3/readme.html

2 Likes

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"]}
5 Likes

Version 1.11.0 Add map_all/2 for working through lists.

Transform every element of a list with a mapping function.
The mapping function must return a result tuple.

If all of the result tuples are tagged :ok, then it returns a list tagged with :ok.
If one or more of the result tuples are tagged :error, it returns the first error.

Examples

  iex> OK.map_all(1..3, &safe_div(6, &1))
  {:ok, [6.0, 3.0, 2.0]}

  iex> OK.map_all([-1, 0, 1], &safe_div(6, &1))
  {:error, :zero_division}

Version 1.11.2 released to fix dialyzer warnings.

Fixes dialyzer warnings

1 Like

OK 2.2.0 released

Added in 2.2

The functions OK.is_success?/1 and OK.is_failure?/1.

Helpful when a series of checks only needs to return a boolean

Added in 2.1

The function OK.check/3 was added
https://hexdocs.pm/ok/2.2.0/OK.html#check/3

Use it to add extra validation to the value in an OK

User
|> Repo.get(user_id)
|> OK.check(fn () -> user.age > 10 end, :too_young_user)
2 Likes

Changed in 2.3

Deprecated OK.is_success?/1 and OK.is_failure?/1 in favor of OK.success?/1 and OK.failure?/1.

Added OK.is_success/1 and OK.is_failure/1 guard macros.

2 Likes