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
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}
Creates a failed result tuple with the given reason.
Examples
iex> OK.failure("reason")
{:error, "reason"}
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}
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}