ok v1.5.0 OK

The OK module enables clean and expressive error handling in pipelines.

Many Elixir libraries follow the tagged tuple convention for functions that will not alway return a valid response. In case of a success the value is returned in an :ok tagged tuple. If the function fails then a reason is returned in an :error tagged tuple.

Calling code the matches on these two possible outcomes.

case my_func(args) do
  {:ok, value} ->
    do_more(value) # continue with subsequent processing
  {:error, reason} ->
    {:error, reason} # return early.
end

OK allows this code to be replaced by a result pipeline.

my_func(args)
~>> do_more

OK treates the combination of tagged tuples {:ok, value} | {:error, reason} as a result monad. The result monad is sometimes know as the try or either monad.

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

Wraps a value as a successful result tuple

Combine multiple functions that may return an error

Result pipe operator. (Result monad bind operator)

Functions

bind(arg, 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}
failure(reason) (macro)

Creates a failed result tuple with the given reason.

Examples

iex> OK.failure("reason")
{:error, "reason"}
success(value) (macro)

Wraps a value as a successful result tuple.

Examples

iex> OK.success(:value)
{:ok, :value}
with(list) (macro)

Combine multiple functions that may return an error.

OK.with/1 is a strict version of the native elixir with special form. It is stricter because it will only handle result tuples. This however allows for more terse expressions.

When combining funcations that may fail the result pipe operator is inflexible in several areas.

  • values can only be passed to the first argument of a function.
  • values can only be passed to the next function.

These limitations can be overcome by OK.with/1

Examples

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

iex> OK.with do
iex>   a <- safe_div(8, 2)
iex>   b <- safe_div(a, 0)
iex>   {:ok, a + b}
iex> end
{:error, :zero_division}
lhs ~>> rhs (macro)

Result pipe operator. (Result monad bind operator)

The result pipe takes the value out of an {:ok, value} tuple and passes it as the first argument to the function call on the right.

Examples

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

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

# x is {:ok, 7} defined in `OKTest`.
iex> x() ~>> double()
{:ok, 14}

The result pipe is most useful when executing a series of operations that may fail.

iex> {:ok, 6} ~>> safe_div(3) ~>> double
{:ok, 4.0}

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

It can be used in several ways. Pipe to a local call. This example is the same as calling double(5)

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

Pipe to a remote call. This example is the same as calling OKTest.double(3)

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

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

Pipe with extra arguments This example is the same as calling OK.safe_div(3, 4)

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

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

Given an anonymous function the following syntax needs to be used.

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

# decrement returns an anonymous function.
# weird I know but was needed as a test case
iex> {:ok, 6} ~>> decrement().(2)
{:ok, 4}