This library provides tools to handle three common return values in Elixir
:ok | {:ok, value} | {:error, reason}- Overview
- Usage
- Differences from Similar Libraries
- Definitions
- Types
- Base
- Helpers
- Mappers
- Known Problems
- Installation
Brex.Result is split into three main components:
Brex.Result.Base- Base provides tools for creating and passing aroundok/errortuples. The tools follow the property: if there’s a success continue the computation, if there’s an error propagate it.Brex.Result.Helpers- Helpers includes tools for modifying the reason inerrortuples. The functions in this module always propagate the success value.Brex.Result.Mappers- Mappers includes tools for applying functions that return:ok | {:ok, val} | {:error, reason}overEnumerables.
Include the line use Brex.Result to import the entire library or import Brex.Result.{Base, Helpers, Mappers} to import the modules individually.
A sampling of functions and examples are provided below. For more in-depth examples see examples.
Other libraries like OK, Monad, and Ok Jose have embraced the concept of monadic error handling and have analogous functions to the ones we have in Brex.Result.Base.
Brex.Result separates itself by:
- Extending support beyond classic monadic functions
- support for
:okas a success value - support for modifying
errorstuple reasons - support for mapping functions that return
ok | {:ok, value} | {:error, reason}overenumerables
- support for
- Respecting Elixir idioms
- encourages use of elixir builtins like the
withstatement when appropriate - provides style guidelines
- actively avoids heavy macro magic that can turn a library into a DSL
- encourages use of elixir builtins like the
- Result tuples:
{:ok, value} | {:error, reason} - Error tuples:
{:error, reason} - OK/Success tuples:
{:ok, value} - Success values:
:ok | {:ok, value} - Propagate a value: Return a value unchanged
Parametrized types
@type s(x) :: {:ok, x} | {:error, any}
@type t(x) :: :ok | s(x)Convenience types
@type p() :: :ok | {:error, any}
@type s() :: s(any)
@type t() :: t(any)Write specs and callbacks usings these shorthands.
# Original
@spec my_fun({:ok, String.t} | {:error, any}, Integer) :: :ok | {:error, any}
# With shorthands
@spec my_fun(Brex.Result.s(String.t), Integer) :: Brex.Result.p()Use Brex.Result.Base.ok/1 to wrap a value in an ok tuple.
iex> 2
...> |> ok
{:ok, 2}Brex.Result.Base.error/1 wraps a value in an error tuple.
iex> :not_found
...> |> error
{:error, :not_found}Don't use ok/1 and error/1 when tuple syntax is more explicit:
# No
ok(2)
# Yes
{:ok, 2}Do use ok/1 and error/1 at the end of a chain of pipes:
# No
val =
arg
|> get_values()
|> transform(other_arg)
{:ok, val}
# Yes
arg
|> get_values()
|> transform(other_arg)
|> okUse Brex.Result.Base.fmap/2 to transform the value within a success tuple. It propagates the error value.
iex> {:ok, 2}
...> |> fmap(fn x -> x + 5 end)
{:ok, 7}
iex> {:error, :not_found}
...> |> fmap(fn x -> x + 5 end)
{:error, :not_found}Use Brex.Result.Base.bind/2 to apply a function to the value within a success tuple. The function must returns a result tuple.
iex> {:ok, 2}
...> |> bind(fn x -> if x > 0, do: {:ok, x + 5}, else: {:error, :neg})
{:ok, 7}
iex> {:ok, -1}
...> |> bind(fn x -> if x > 0, do: {:ok, x + 5}, else: {:error, :neg})
{:error, :neg}
iex> {:error, :not_found}
...> |> bind(fn x -> if x > 0, do: {:ok, x + 5}, else: {:error, :neg})
{:error, :not_found}Infix bind is given for convience as Brex.Result.Base.~>/2
iex> {:ok, [[1, 2, 3, 4]}
...> ~> Enum.member(2)
...> |> fmap(fn x -> if x, do: :found_two, else: :not_found end)
{:ok, :found_two}Avoid single ~>s and only use ~> when the function argument is named and fits onto one line.
# No
{:ok, file}
~> File.read()
# Yes
bind({:ok, file}, &File.read/1)
# No
{:ok, val}
~> (fn x -> if x > 0, do: {:ok, x}, else: {:error, neg}).()
~> insert_amount()
# Yes
{:ok, val}
|> bind(fn x -> if x > 0, do: {:ok, x}, else: {:error, neg})
~> insert_amount()Brex.Result.Helpers.map_error/2 allows you to transform the reason in an error tuple.
iex> {:error, 404}
...> |> map_error(fn reason -> {:invalid_response, reason} end)
{:error, {:invalid_reponse, 404}}
iex> {:ok, 2}
...> |> map_error(fn reason -> {:invalid_response, reason} end)
{:ok, 2}
iex> :ok
...> |> map_error(fn reason -> {:invalid_response, reason} end)
:okBrex.Result.Helpers.mask_error/2 disregards the current reason and replaces it with the second argument.
iex> {:error, :not_found}
...> |> mask_error(:failure)
{:error, :failure}Brex.Result.Helpers.convert_error/3 converts an error into a success value if the reason matches the second argument.
iex> {:error, :not_found}
...> |> convert_error(:not_found)
:ok
iex> {:error, :not_found}
...> |> convert_error(:not_found, default)
{:ok, default}Brex.Result.Helpers.log_error/3 logs on error. It automatically includes the reason in the log metadata.
iex> {:error, :not_found}
...> |> log_error("There was a problem", level: :warn)
{:error, :not_found}Brex.Result.Helpers.normalize_error/2 converts a naked :error atom into an error tuple. It's good for functions from outside libraries.
iex> :error
...> |> normalize_error(:not_found)
{:error, :not_found}
iex> {:ok, 2}
...> |> normalize_error(:not_found)
{:ok, 2}
iex> :ok
...> |> normalize_error(:not_found)
:okBrex.Result.Mappers.map_while_success/2, Brex.Result.Mappers.each_while_success/2, Brex.Result.Mappers.reduce_while_success/3 all mimic the Enum functions Enum.map/2, Enum.each/2, Enum.reduce/3, but take a function that returns :ok | {:ok, value} | {:error, reason} as the mapping/reducing argument. Each of these functions produce a success value containing the final result or the first error.
- Credo complains pipe chain is not started with raw value when preceeded by
~>.
The package can be installed by adding brex_result to your list of dependencies in mix.exs:
def deps do
[
{:brex_result, "~> 0.4.1"}
]
end