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/0s, 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 errorish() ::
  :error
  | Exception.t()
  | {:error, any()}
  | {:error, any(), any()}
  | {:error, any(), any(), any()}
  | {:error, any(), any(), any(), any()}
  | {:error, any(), any(), any(), any(), any()}
@type ok(v) :: {:ok, v}
@type okish() ::
  :ok
  | {:ok, any()}
  | {:ok, any(), any()}
  | {:ok, any(), any(), any()}
  | {:ok, any(), any(), any(), any()}
  | {:ok, any(), any(), any(), any(), any()}
@type result(v, e) :: ok(v) | error(e)

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"], []}
@spec error(e) :: result(none(), e) when e: var

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/0s, 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!"}}
Link to this macro

is_errorish(val)

View Source (macro)

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.

Link to this macro

is_valish(val)

View Source (macro)

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.

@spec ok(v) :: result(v, none()) when v: var

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}}