View Source Lace.Result (Lace v0.0.3)
A module for working with result tuples in a clear, composable, and pipeline-friendly manner.
Result Representation
This module defines t:result/0
as strictly {:ok, any()} | {:error, any()}
,
and the majority of functions within this module operate exclusively on these values.
Several standard library functions and various packages in the Elixir ecosystem
return values such as :ok
, {:ok, val, extra_info}
, :error
, and {:error, code, extra_info}
,
along with other result-like but inconsistently structured values.
We refer to these values as okish (representing successful computations)
and errorish (indicating failures), collectively termed resultish.
You can use from/1
to convert this diverse array of values into consistent t:result/0
types,
enabling you to leverage the utilities provided by this module.
Best Practices for Error Values
For error handling, we advocate for the best practice of using {:error, <exception struct>}
,
particularly in libraries and packages intended for use by other code.
One of the first public mentions of this approach can be found in a post on Michał Muskała's blog, with a from Andrea Leopardi (@whatyouhide, now a member of the Elixir Core Team) https://web.archive.org/web/20180414015950/http://michal.muskala.eu/2017/02/10/error-handling-in-elixir-libraries.html#comments-whatyouhide-errors
This pattern is adopted by several high-profile and well-architected libraries, including Redix, Mint, Req, among others. It offers several advantages including: a consistent shape for pattern matching, being able to trivially raise exceptions or extract messages from error tuples, and the ability to include additional fields with details in the error value.
Summary
Functions
Chains a result with a function that returns a new result.
Collapses a list of results into a single result.
Collect a list of results into a tuple of ok values and errors.
Turns a value into an error t:result()
by wrapping it in an {:error, ...}
tuple.
Tries its best to transform any value into a well-formed t:result
tuple,
of the kind handled by the functions in this module.
It does so by based on the value's resultishness: errorish values are converted
to t:error/0
s, while any other value is converted to t:ok/0
.
Returns true
if val
is errorish (including a well-formed error value).
Such values would be converted into an {:error, _}
tuple by the from/1
function.
Can be used in guard tests.
Returns true
if val
is okish (including a well-formed ok value).
Such values would be converted into an {:ok, _}
tuple by the from/1
function.
Can be used in guard tests.
Returns true
if val
is neither okish nor errorish.
Such values would be converted into an {:ok, _}
tuple by the from/
function.
Can be used in guard tests.
Turns a value into an ok t:result()
by wrapping it in an {:ok, ...}
tuple.
Types
@type error(e) :: {:error, e}
@type ok(v) :: {:ok, v}
Functions
@spec chain(result(v1, e1), (v1 -> result(v2, e2))) :: result(v2, e1 | e2) when v1: var, e1: var, v2: var, e2: var
Chains a result with a function that returns a new result.
If the initial result is {:ok, value}
, the provided function is called with the value,
and its result is returned. If the initial result is {:error, error}
, the error is returned
without calling the function.
Great for building lazy pipelines of fallible computations.
Examples
iex> chain({:ok, 1}, fn x -> {:ok, x + 1} end)
{:ok, 2}
iex> chain({:error, "missing number"}, fn x -> {:ok, x + 1} end)
{:error, "missing number"}
iex> {:ok, 1}
...> |> chain(fn x -> {:ok, x + 1} end)
...> |> chain(fn x -> {:ok, x * 2} end)
...> |> chain(fn x -> {:error, :nan} end)
...> |> chain(fn x -> {:ok, x * 10} end)
{:error, :nan}
Collapses a list of results into a single result.
Stops at the first error encountered.
Examples
iex> Lace.Result.collapse([{:ok, 1}, {:ok, 2}, {:ok, 3}])
{:ok, [1, 2, 3]}
iex> Lace.Result.collapse([{:ok, 1}, {:error, 2}, {:ok, 3}, {:error, 4}])
{:error, 2}
@spec collect([result(v, e)]) :: {[v], [e]} when v: var, e: var
Collect a list of results into a tuple of ok values and errors.
Examples
iex> Lace.Result.collect([{:ok, 1}, {:error, 2}, {:ok, 3}])
{[1, 3], [2]}
iex> Lace.Result.collect([{:ok, "green"}])
{["green"], []}
Turns a value into an error t:result()
by wrapping it in an {:error, ...}
tuple.
Note that this always returns an :error tuple, it does not try to infer the most
appropriate result type. Use from/1
for that.
Examples
iex> :http_error |> error()
{:error, :http_error}
iex> {:ok, 12} |> error()
{:error, {:ok, 12}}
Tries its best to transform any value into a well-formed t:result
tuple,
of the kind handled by the functions in this module.
It does so by based on the value's resultishness: errorish values are converted
to t:error/0
s, while any other value is converted to t:ok/0
.
Useful to transform resultish values into proper result values,
which can then be used in chainable and linear Lace.Result
pipelines.
Examples:
iex> from(12)
{:ok, 12}
iex> from(:ok)
{:ok, nil}
iex> from(:error)
{:error, nil}
iex> from({:ok, 12})
{:ok, 12}
iex> from({:error, :reason, %{trace_id: 100}})
{:error, {:reason, %{trace_id: 100}}}
iex> from(RuntimeError.exception("oh-oh!"))
{:error, %RuntimeError{message: "oh-oh!"}}
Returns true
if val
is errorish (including a well-formed error value).
Such values would be converted into an {:error, _}
tuple by the from/1
function.
Can be used in guard tests.
Returns true
if val
is okish (including a well-formed ok value).
Such values would be converted into an {:ok, _}
tuple by the from/1
function.
Can be used in guard tests.
Returns true
if val
is neither okish nor errorish.
Such values would be converted into an {:ok, _}
tuple by the from/
function.
Can be used in guard tests.
Turns a value into an ok t:result()
by wrapping it in an {:ok, ...}
tuple.
Note that this always returns an :ok tuple, it does not try to infer the most
appropriate result type. Use from/1
for that.
Examples
iex> 2 |> ok()
{:ok, 2}
iex> {:error, :http_error} |> ok()
{:ok, {:error, :http_error}}