OK
Elegant error handling in elixir pipelines. See Handling Errors in Elixir for a more detailed explanation
Documentation for OK is available on hexdoc
Installation
Available in Hex, the package can be installed as:
Add ok to your list of dependencies in
mix.exs
:def deps do [{:ok, "~> 1.5.0"}] end
Usage
The erlang convention for functions that can fail is to return a result tuple.
A result tuple is a two-tuple tagged either as a success(:ok
) or a failure(:error
).
The OK module works with result tuples by treating them as a result monad.
{:ok, value} | {:error, reason}
Forum discussion on :ok/:error
Result pipelines ~>>
This macro allows pipelining result tuples through a pipeline of functions.
The ~>>
macro is the is equivalent to bind/flat_map in other languages.
import OK, only: ["~>>": 2]
def get_employee_data(file, name) do
{:ok, file}
~>> File.read
~>> Poison.decode
~>> Dict.fetch(name)
end
def handle_user_data({:ok, data}), do: IO.puts("Contact at #{data["email"]}")
def handle_user_data({:error, :enoent}), do: IO.puts("File not found")
def handle_user_data({:error, {:invalid, _}}), do: IO.puts("Invalid JSON")
def handle_user_data({:error, :key_not_found}), do: IO.puts("Could not find employee")
get_employee_data("my_company/employees.json")
|> handle_user_data
Code structured like this is an example of railway programming.
Forum discussion on error handling in pipelines
Result blocks with
For situations when the pipeline macro is not sufficiently flexible.
To extract a value for an ok tuple use the <-
operator.
require OK
OK.with do
user <- fetch_user(1)
cart <- fetch_cart(1)
order = checkout(cart, user)
save_order(order)
end
Ok.with/1
supports an else block that can be used for handling error values.
OK.with do
a <- safe_div(8, 2)
_ <- safe_div(a, 0)
else
:zero_division -> # matches on reason
{:ok, :inf} # must return a new success or failure
end
Unlike native with any unmatched error case does not through an error and will just be passed as the return value
The cart example above is equivalent to
case fetch_user(1) do
{:ok, user} ->
case fetch_cart(1) do
{:ok, cart} ->
order = checkout(cart, user)
save_order(order)
{:error, reason} ->
{:error, reason}
end
{:error, reason} ->
{:error, reason}
end
Forum discussion on with
naming
Semantic matches
OK
provides macros for matching on success and failure cases.
This allows for code to check if a result returned from a function was a success or failure.
This check can be done without knowledge about how the result is structured to represent a success or failure
import OK, only: [success: 2, failure: 2]
case fetch_user(id) do
success(user) ->
user
failure(:not_found) ->
create_guest_user()
end
Similar Libraries
For reference.
Possible extensions to include implementing bind on structs so that errors can be better handled. Implement a catch functionality for functions that error. Implement existing monad library protocols so can extend similar DB functionality e.g. slick