ok v1.6.1 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.
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
Require a variable not to be nil
Wraps a value as a successful result tuple
DEPRECATED: OK.try
has been replaced with OK.with
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
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"}
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}
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.
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}