ok v1.9.0 OK

The OK module enables clean and expressive error handling when coding with idiomatic :ok/:error tuples. We’ve included many examples in the function docs here, but you can also check out the README for more details and usage.

Feel free to open an issue for any questions that you have.

Link to this section Summary

Functions

Takes a result tuple and a next function. If the result tuple is tagged as a success then its value will be passed to the next function. If the tag is failure then the next function is skipped

Creates a failed result tuple with the given reason

Lightweight notation for working with the values from serval failible components

Require a variable not to be nil

Wraps a value as a successful result tuple

Handle return value from several failible functions

Composes multiple functions similar to Elixir’s native with construct

The OK result pipe operator ~>>, or result monad bind operator, is similar to Elixir’s native |> except it is used within happy path. It takes the value out of an {:ok, value} tuple and passes it as the first argument to the function call on the right

Link to this section Functions

Link to this function bind(failure, func)

Takes a result tuple and a next function. If the result tuple is tagged as a success then its value will be passed to the next function. If the tag is failure then the next function is skipped.

Examples

iex> OK.bind({:ok, 2}, fn (x) -> {:ok, 2 * x} end)
{:ok, 4}

iex> OK.bind({:error, :some_reason}, fn (x) -> {:ok, 2 * x} end)
{:error, :some_reason}
Link to this macro failure(reason) (macro)

Creates a failed result tuple with the given reason.

Examples

iex> OK.failure("reason")
{:error, "reason"}
Link to this macro for(list) (macro)

Lightweight notation for working with the values from serval failible components.

Values are extracted from an ok tuple using the in (<-) operator. Any line using this operator that trys to match on an error tuple will result in early return.

If all bindings can be made, i.e. all functions returned {:ok, value}, then the after block is executed to return the final value.

Return values from the after block are wrapped as an ok result, unless they are already a result tuple. The return value of a for comprehension is always a result monad

iex> OK.for do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 2)
...> after
...>   a + b
...> end
{:ok, 6.0}

iex> OK.for do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 2)
...> after
...>   OK.success(a + b)
...> end
{:ok, 6.0}

iex> OK.for do
...>   a <- safe_div(8, 2)
...>   _ <- safe_div(a, 2)
...> after
...>   {:error, :something_else}
...> end
{:error, :something_else}

Regular matching using the = operator is also available, for calculating intermediate values.

iex> OK.for do
...>   a <- safe_div(8, 2)
...>   b = 2.0
...> after
...>   a + b
...> end
{:ok, 6.0}

iex> OK.for do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 0) # error here
...> after
...>   a + b               # does not execute this line
...> end
{:error, :zero_division}
Link to this function required(value, reason \\ :value_required)

Require a variable not to be nil.

Optionally provide a reason why variable is required.

Example

iex> OK.required(:some)
{:ok, :some}

iex> OK.required(nil)
{:error, :value_required}

iex> OK.required(Map.get(%{}, :port), :port_number_required)
{:error, :port_number_required}
Link to this macro success(value) (macro)

Wraps a value as a successful result tuple.

Examples

iex> OK.success(:value)
{:ok, :value}
Link to this macro try(list) (macro)

Handle return value from several failible functions.

Values are extracted from an ok tuple using the in (<-) operator. Any line using this operator that trys to match on an error tuple will result in early return.

If all bindings can be made, i.e. all functions returned {:ok, value}, then the after block is executed to return the final value.

If any bind fails then the rescue block will be tried.

Note: return value from after will be returned unwrapped

Examples

iex> OK.try do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 2)
...> after
...>   a + b
...> rescue
...>   :zero_division ->
...>     :nan
...> end
6.0

iex> OK.try do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 0)
...> after
...>   a + b
...> rescue
...>   :zero_division ->
...>     :nan
...> end
:nan
Link to this macro with(list) (macro)

Composes multiple functions similar to Elixir’s native with construct.

OK.with/1 enables more terse and readable expressions however, eliminating noise and regaining precious horizontal real estate. This makes OK.with statements simpler, more readable, and ultimately more maintainable.

It does this by extracting result tuples when using the <- operator.

iex> OK.with do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 2)
...>   OK.success b
...> end
{:ok, 2.0}

In above example, the result of each call to safe_div/2 is an :ok tuple from which the <- extract operator pulls the value and assigns to the variable a. We then do the same for b, and to indicate our return value we use the OK.success/1 macro.

We could have also written this with a raw :ok tuple:

iex> OK.with do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 2)
...>   {:ok, b}
...> end
{:ok, 2.0}

Or even this:

iex> OK.with do
...>   a <- safe_div(8, 2)
...>   _ <- safe_div(a, 2)
...> end
{:ok, 2.0}

In addition to this, regular matching using the = operator is also available:

iex> OK.with do
...>   a <- safe_div(8, 2)
...>   b = 2.0
...>   OK.success a + b
...> end
{:ok, 6.0}

Error tuples are returned from the expression:

iex> OK.with do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 0) # error here
...>   {:ok, a + b}        # does not execute this line
...> end
{:error, :zero_division}

OK.with also provides else blocks where you can pattern match on the extracted error values, which is useful for wrapping or correcting errors:

iex> OK.with do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 0) # returns {:error, :zero_division}
...>   {:ok, a + b}
...> else
...>   :zero_division -> OK.failure "You cannot divide by 0."
...> end
{:error, "You cannot divide by 0."}

Combining OK.with and ~>>

Because the OK.pipe operator (~>>) also uses result monads, you can now pipe safely within an OK.with block:

iex> OK.with do
...>   a <- {:ok, 100}
...>        ~>> safe_div(10)
...>        ~>> safe_div(5)
...>   b <- safe_div(64, 32)
...>        ~>> double()
...>   OK.success a + b
...> end
{:ok, 6.0}

iex> OK.with do
...>   a <- {:ok, 100}
...>        ~>> safe_div(10)
...>        ~>> safe_div(0)   # error here
...>   b <- safe_div(64, 32)
...>        ~>> double()
...>   OK.success a + b
...> end
{:error, :zero_division}

Remarks

Notice that in all of these examples, we know this is a happy path operation because we are inside of the OK.with block. But it is much more elegant, readable and DRY, as it eliminates large numbers of superfluous :ok tags that would normally be found in native with blocks.

Also, OK.with does not have trailing commas on each line. This avoids compilation errors when you accidentally forget to add/remove a comma when coding.

Be sure to check out ok_test.exs tests for more examples.

Link to this macro lhs ~>> rhs (macro)

The OK result pipe operator ~>>, or result monad bind operator, is similar to Elixir’s native |> except it is used within happy path. It takes the value out of an {:ok, value} tuple and passes it as the first argument to the function call on the right.

It can be used in several ways.

Pipe to a local call.
(This is equivalent to calling double(5))

iex> {:ok, 5} ~>> double()
{:ok, 10}

Pipe to a remote call.
(This is equivalent to calling OKTest.double(5))

iex> {:ok, 5} ~>> OKTest.double()
{:ok, 10}

iex> {:ok, 5} ~>> __MODULE__.double()
{:ok, 10}

Pipe with extra arguments.
(This is equivalent to calling safe_div(6, 2))

iex> {:ok, 6} ~>> safe_div(2)
{:ok, 3.0}

iex> {:ok, 6} ~>> safe_div(0)
{:error, :zero_division}

It also works with anonymous functions.

iex> {:ok, 3} ~>> (fn (x) -> {:ok, x + 1} end).()
{:ok, 4}

iex> {:ok, 6} ~>> decrement().(2)
{:ok, 4}

When an error is returned anywhere in the pipeline, it will be returned.

iex> {:ok, 6} ~>> safe_div(0) ~>> double()
{:error, :zero_division}

iex> {:error, :previous_bad} ~>> safe_div(0) ~>> double()
{:error, :previous_bad}