OkThen.Result (ok_then v1.1.0)

Functions to aid processing of tagged tuples in pipelines.

Examples

iex> 1
...> |> Result.from()                # {:ok, 1}
...> |> Result.map(& &1 * 2)         # {:ok, 2}
...> |> Result.then(& {:ok, &1 + 1}) # {:ok, 3}
...> |> Result.unwrap_or_else(0)
3

iex> "Oh no!"
...> |> Result.from_error()          # {:error, "Oh no!"}
...> |> Result.map(& &1 * 2)         # {:error, "Oh no!"}
...> |> Result.then(& {:ok, &1 + 1}) # {:error, "Oh no!"}
...> |> Result.unwrap_or_else(0)
0

iex> {:ok, 1}
...> |> Result.map(fn
...>      1 -> nil
...>      x -> x * 2
...>    end)                         # :none
...> |> Result.default(3)            # {:ok, 3}
...> |> Result.unwrap_or_else(0)
3

iex> {:ok, 2}
...> |> Result.map(fn
...>      1 -> nil
...>      x -> x * 2
...>    end)                         # {:ok, 4}
...> |> Result.default(3)            # {:ok, 4}
...> |> Result.unwrap_or_else(0)
4

iex> {:error, "Oh no!"}
...> |> Result.map(fn
...>      1 -> nil
...>      x -> x * 2
...>    end)                         # {:error, "Oh no!"}
...> |> Result.default(3)            # {:error, "Oh no!"}
...> |> Result.unwrap_or_else(0)
0

iex> {:error, "Oh no!"}
...> |> Result.error_then(fn "Oh no!" -> :error end) # :error
...> |> Result.or_else(fn -> {:ok, 0} end)           # {:ok, 0}
...> |> Result.unwrap!()
0

iex> {:ok, 1}
...> |> Result.error_then(fn "Oh no!" -> :error end) # {:ok, 1}
...> |> Result.or_else(fn -> {:ok, 0} end)           # {:ok, 1}
...> |> Result.unwrap!()
1

Link to this section Summary

Guards

Returns true if result is tagged :error.

Returns true if result is tagged :none.

Returns true if result is tagged :ok.

Returns true if result is tagged with the specified tag atom.

Returns true if result is a tagged tuple.

Functions (:ok)

If result is tagged with :ok, passes the wrapped value into the provided function (if provided) and returns :none.

If result is tagged :ok, passes the wrapped value into the provided function. If func_or_value returns a truthy value, result is returned unchanged. Otherwise, returns :none. If func_or_value is not a function, then it is used directly as the check value.

If result is tagged :ok, transforms the wrapped value by passing it into the provided mapping function, and replacing it with the returned value. If func_or_value is not a function, then it is used directly as the new value.

If result is not tagged with :ok, passes the tag and wrapped value into the provided function and returns the result. If the function has arity 1, then only the wrapped value is passed in. If func_or_value is not a function, then it is used directly as the new value.

If result is tagged :ok, replaces the tag with new_tag, returning a new tagged tuple.

If result is tagged with :ok, passes the wrapped value into the provided function and returns result unchanged. The return value of the function is ignored.

If result is tagged :ok, passes the wrapped value into func_or_value and returns the result. If a function is not provided, the argument at the same position is returned as-is.

Same as unwrap_or_else/2, except raises ArgumentError if result is not tagged :ok.

Returns the wrapped value if result is tagged :ok. Otherwise, passes the tag and wrapped value into the provided function and returns the result. If the function has arity 1, then only the wrapped value is passed in. An arity-0 function is also accepted. If func_or_value is not a function, then it is used directly as the new value.

Functions (:error)

If result is tagged with :error, passes the wrapped value into the provided function (if provided) and returns :none.

If result is tagged :error, passes the wrapped value into the provided function. If func_or_value returns a truthy value, result is returned unchanged. Otherwise, returns :none. If func_or_value is not a function, then it is used directly as the check value.

If result is tagged :error, transforms the wrapped value by passing it into the provided mapping function, and replacing it with the returned value. If func_or_value is not a function, then it is used directly as the new value.

If result is not tagged with :error, passes the tag and wrapped value into the provided function and returns the result. If the function has arity 1, then only the wrapped value is passed in. If func_or_value is not a function, then it is used directly as the new value.

If result is tagged :error, replaces the tag with new_tag, returning a new tagged tuple.

If result is tagged with :error, passes the wrapped value into the provided function and returns result unchanged. The return value of the function is ignored.

If result is tagged :error, passes the wrapped value into func_or_value and returns the result. If a function is not provided, the argument at the same position is returned as-is.

Same as error_unwrap_or_else/2, except raises ArgumentError if result is not tagged :error.

Returns the wrapped value if result is tagged :error. Otherwise, passes the tag and wrapped value into the provided function and returns the result. If the function has arity 1, then only the wrapped value is passed in. An arity-0 function is also accepted. If func_or_value is not a function, then it is used directly as the new value.

Functions (:none)

If result is tagged :none, returns func_or_value wrapped as an :ok result. Otherwise, returns result. If func_or_value is a function, the returned value is used as the new value.

Same as default/2, except raises ArgumentError if func_or_value returns nil.

If result is tagged :none, returns func_or_value wrapped as a result with the given tag. Otherwise, returns result. If func_or_value is a function, the returned value is used as the new value.

Same as default_as/3, except raises ArgumentError if func_or_value returns nil.

If result is tagged :none, returns func_or_value wrapped as an :error result. Otherwise, returns result. If func_or_value is a function, the returned value is used as the new value.

Same as default_error/2, except raises ArgumentError if func_or_value returns nil.

If result is tagged :none, replaces the tag with new_tag.

If result is tagged :none, calls func_or_value and returns the result. If func_or_value is not a function, then it is returned as-is.

Functions (any tag)

If result is tagged with the specified tag atom, passes the wrapped value into the provided function (if provided) and returns :none.

If result is tagged with the specified tag atom, passes the wrapped value into the provided function. If func_or_value returns a truthy value, result is returned unchanged. Otherwise, returns :none. If func_or_value is not a function, then it is used directly as the check value.

If result is tagged with the specified tag atom, transforms the wrapped value by passing it into the provided mapping function, and replacing it with the returned value. If a function is not provided, the argument at the same position is used as the new value.

If result is not tagged with the specified tag atom, passes the tag and wrapped value into the provided function and returns the result. If the function has arity 1, then only the wrapped value is passed in. An arity-0 function is also accepted. If func_or_value is not a function, then it is used directly as the new value.

If result is tagged with the specified tag atom, replaces the tag with new_tag, returning a new tagged tuple.

If result is tagged with the specified tag atom, passes the wrapped value into the provided function and returns result unchanged. The return value of the function is ignored.

If result is tagged with the specified tag atom, passes the wrapped value into the provided function and returns the result. If func_or_value is not a function, then it is returned as-is.

Same as tagged_unwrap_or_else/3, except raises ArgumentError if result is not tagged with the specified tag atom.

Returns the wrapped value if result is tagged with the specified tag atom. Otherwise, passes the tag and wrapped value into the provided function and returns the result. If the function has arity 1, then only the wrapped value is passed in. An arity-0 function is also accepted. If func_or_value is not a function, then it is used directly as the new value.

Functions

Converts value into a maybe(v) result: {:ok, value} | :none

Same as from/1, except raises ArgumentError if value is nil.

Converts value into a maybe_is(tag) result: {atom(), any()} | :none

Same as from_as/2, except raises ArgumentError if value is nil.

Converts value into a maybe_error(e) result: {:error, value} | :none

Same as from_error/1, except raises ArgumentError if value is nil.

Converts result from a variety of accepted result-like terms into an atom or a two-element tagged tuple.

Same as normalize/1, except raises ArgumentError if value is untagged.

Link to this section Types

Specs

error(t) :: {:error, t}

Specs

maybe() :: maybe_is(:ok)

Specs

maybe(t) :: maybe_is(:ok, t)

Specs

maybe(t, e) :: ok(t) | error(e) | :none
Link to this type

maybe_error()

Specs

maybe_error() :: maybe_is(:error)
Link to this type

maybe_error(e)

Specs

maybe_error(e) :: maybe_is(:error, e)

Specs

maybe_is(t) :: t | :none
Link to this type

maybe_is(t, v)

Specs

maybe_is(t, v) :: {t, v} | :none

Specs

ok(t) :: {:ok, t}

Specs

ok_or(e) :: :ok | error(e)

Specs

ok_or(t, e) :: ok(t) | error(e)
Link to this type

result_input()

Specs

result_input() :: atom() | tuple()

Specs

tagged() :: atom() | {atom(), any()}

Link to this section Guards

Link to this macro

is_error(value)

(macro)

Returns true if result is tagged :error.

Equivalent to is_tagged(value, :error). See is_tagged/2.

Examples

iex> Result.is_error(:error)
true

iex> Result.is_error({:error, "hello"})
true

iex> Result.is_error({:ok, "hello"})
false
Link to this macro

is_none(value)

(macro)

Returns true if result is tagged :none.

Equivalent to is_tagged(value, :none). See is_tagged/2.

Examples

iex> Result.is_none(:none)
true

iex> Result.is_none({:ok, "hello"})
false

iex> Result.is_none({:error, "hello"})
false
Link to this macro

is_ok(value)

(macro)

Returns true if result is tagged :ok.

Equivalent to is_tagged(value, :ok). See is_tagged/2.

Examples

iex> Result.is_ok(:ok)
true

iex> Result.is_ok({:ok, "hello"})
true

iex> Result.is_ok({:error, "hello"})
false
Link to this macro

is_tagged(value, tag)

(macro)

Returns true if result is tagged with the specified tag atom.

Examples

iex> Result.is_tagged(:ok, :ok)
true

iex> Result.is_tagged({:ok, "hello"}, :ok)
true

iex> Result.is_tagged({:error, "hello"}, :ok)
false

iex> Result.is_tagged({:ok, 1, 2}, :ok)
true

iex> Result.is_tagged({:ok, {1, 2}}, :ok)
true

iex> Result.is_tagged({:strange, "hello"}, :strange)
true

iex> hello = fn -> "hello" end
...> hello.() |> Result.is_tagged(:ok)
false
Link to this macro

is_tagged_tuple(value)

(macro)

Returns true if result is a tagged tuple.

Examples

iex> Result.is_tagged_tuple(:ok)
true

iex> Result.is_tagged_tuple({:ok, "hello"})
true

iex> Result.is_tagged_tuple({:error, "hello"})
true

iex> Result.is_tagged_tuple({:ok, 1, 2})
true

iex> Result.is_tagged_tuple({:ok, {1, 2}})
true

iex> Result.is_tagged_tuple({:strange, "hello"})
true

iex> Result.is_tagged_tuple({"ok", "hello"})
false

iex> func = fn -> "hello" end
...> func.() |> Result.is_tagged_tuple()
false

iex> func = fn -> nil end
...> func.() |> Result.is_tagged_tuple()
false

iex> Result.is_tagged_tuple({nil, "hello"})
false

Link to this section Functions (:ok)

Link to this function

consume(result, function \\ &(&1))

Specs

consume(result_input(), (any() -> any())) :: result_input() | :none

If result is tagged with :ok, passes the wrapped value into the provided function (if provided) and returns :none.

If result is not tagged with :ok, result is returned as-is.

Equivalent to tagged_consume(result, :ok, func_or_value). See tagged_consume/3.

Examples

iex> :ok |> Result.consume()
:none

iex> :ok |> Result.consume(fn -> "hello" end)
:none

iex> :ok |> Result.consume(fn {} -> "hello" end)
:none

iex> {:ok, 1} |> Result.consume(fn 1 -> "hello" end)
:none

iex> {:ok, 1, 2} |> Result.consume(fn 1, 2 -> "hello" end)
** (ArgumentError) Value-mapping function must have arity between 0 and 1.

iex> {:ok, 1, 2} |> Result.consume(fn {1, 2} -> "hello" end)
:none

iex> :error |> Result.consume(fn {} -> "hello" end)
:error

iex> {:error, 1} |> Result.consume(fn 1 -> "hello" end)
{:error, 1}

iex> "bare value" |> Result.consume()
"bare value"
Link to this function

filter(result, func_or_value)

Specs

If result is tagged :ok, passes the wrapped value into the provided function. If func_or_value returns a truthy value, result is returned unchanged. Otherwise, returns :none. If func_or_value is not a function, then it is used directly as the check value.

Equivalent to tagged_filter(result, :ok, func_or_value). See tagged_filter/3.

Examples

iex> {:ok, "hello"} |> Result.filter(&String.length(&1) == 5)
{:ok, "hello"}

iex> {:ok, "hello"} |> Result.filter(&String.length(&1) == 0)
:none

iex> {:ok, "hello"} |> Result.filter(fn -> true end)
{:ok, "hello"}

iex> {:ok, "hello"} |> Result.filter(fn -> false end)
:none

iex> {:ok, "hello"} |> Result.filter(true)
{:ok, "hello"}

iex> {:ok, "hello"} |> Result.filter(false)
:none

iex> :some |> Result.filter(&String.length(&1) == 0)
:some

iex> :error |> Result.filter(&String.length(&1) == 0)
:error

iex> nil |> Result.filter(&String.length(&1) == 0)
nil
Link to this function

map(result, func_or_value)

Specs

map(t, OkThen.Result.Private.func_or_value(out)) :: t | :ok | ok(out)
when t: result_input(), out: any()

If result is tagged :ok, transforms the wrapped value by passing it into the provided mapping function, and replacing it with the returned value. If func_or_value is not a function, then it is used directly as the new value.

If the new value would be nil, then :none is returned as the result instead. Consider piping into |> none_then({:ok, nil}) if you really want {:ok, nil}. See none_then/2.

If result is not tagged :ok, result is returned as-is.

Equivalent to tagged_map(result, :ok, func_or_value). See tagged_map/3.

Examples

iex> :ok |> Result.map("hello")
{:ok, "hello"}

iex> {:ok, 1} |> Result.map("hello")
{:ok, "hello"}

iex> {:ok, 1} |> Result.map(nil)
:none

iex> :none |> Result.map("hello")
:none

iex> :ok |> Result.map(fn {} -> "hello" end)
{:ok, "hello"}

iex> {:ok, 1} |> Result.map(fn 1 -> "hello" end)
{:ok, "hello"}

iex> {:ok, 1, 2} |> Result.map(fn {1, 2} -> "hello" end)
{:ok, "hello"}

iex> {:ok, 1, 2} |> Result.map(fn 1, 2 -> "hello" end)
** (ArgumentError) Value-mapping function must have arity between 0 and 1.

iex> {:ok, 1, 2} |> Result.map(fn {1, 2} -> {} end)
:ok

iex> :error |> Result.map(fn _ -> "hello" end)
:error

iex> {:error, 1} |> Result.map(fn _ -> "hello" end)
{:error, 1}

iex> {:error, 1, 2} |> Result.map(fn _ -> "hello" end)
{:error, 1, 2}

iex> :none |> Result.map(fn _ -> "hello" end)
:none

iex> :something_else |> Result.map(fn _ -> "hello" end)
:something_else

iex> "bare value" |> Result.map(fn _ -> "hello" end)
"bare value"

iex> "bare value" |> Result.map("hello")
"bare value"
Link to this function

or_else(result, func_or_value)

Specs

or_else(result_input(), OkThen.Result.Private.func_or_value(atom(), out)) :: out
when out: any()

If result is not tagged with :ok, passes the tag and wrapped value into the provided function and returns the result. If the function has arity 1, then only the wrapped value is passed in. If func_or_value is not a function, then it is used directly as the new value.

If result is tagged with :ok, result is returned as-is.

Use this function in a pipeline to branch the unhappy path into another function, or as a kind of case expression to handle multiple types of result without the boilerplate of copying through a successful result.

Be aware that no attempt is made to ensure the return value from the function is a tagged tuple. However, all functions are tolerant of untagged results, and on input will interpret them as an {:untagged, value} tuple.

Equivalent to tagged_or_else(result, :ok, func_or_value). See tagged_or_else/3.

Examples

iex> :error
...> |> Result.or_else(fn
...>   :error, {} -> {:ok, "hello"}
...> end)
{:ok, "hello"}

iex> {:error, 1}
...> |> Result.or_else(fn
...>   :error, 1 -> {:ok, "hello"}
...> end)
{:ok, "hello"}

iex> {:error, 1, 2}
...> |> Result.or_else(fn
...>   :error, {1, 2} -> {:ok, "matched error"}
...>   :none, {} -> {:ok, "matched none"}
...> end)
{:ok, "matched error"}

iex> :none
...> |> Result.or_else(fn
...>   :error, {1, 2} -> {:ok, "matched error"}
...>   :none, {} -> {:ok, "matched none"}
...> end)
{:ok, "matched none"}

iex> {:ok, "just ok"}
...> |> Result.or_else(fn
...>   :error, {1, 2} -> {:error, "matched error"}
...>   :none, {} -> {:ok, "matched none"}
...> end)
{:ok, "just ok"}

iex> {:error, 1, 2}
...> |> Result.or_else(fn
...>   {1, 2} -> {:ok, "matched two terms"}
...>   1 -> {:ok, "matched one term"}
...>   {} -> {:ok, "matched no terms"}
...> end)
{:ok, "matched two terms"}

iex> {:error, 1}
...> |> Result.or_else(fn
...>   {1, 2} -> {:ok, "matched two terms"}
...>   1 -> {:ok, "matched one term"}
...>   {} -> {:ok, "matched no terms"}
...> end)
{:ok, "matched one term"}

iex> :error
...> |> Result.or_else(fn
...>   {1, 2} -> {:ok, "matched two terms"}
...>   1 -> {:ok, "matched one term"}
...>   {} -> {:ok, "matched no terms"}
...> end)
{:ok, "matched no terms"}

iex> :none
...> |> Result.or_else(fn
...>   {1, 2} -> {:ok, "matched two terms"}
...>   1 -> {:ok, "matched one term"}
...>   {} -> {:ok, "matched no terms"}
...> end)
{:ok, "matched no terms"}

iex> :error |> Result.or_else({:ok, "hello"})
{:ok, "hello"}

iex> {:error, 1} |> Result.or_else({:ok, "hello"})
{:ok, "hello"}

iex> {:error, 1} |> Result.or_else("bare value")
"bare value"

iex> "bare value" |> Result.or_else(fn _ -> :none end)
:none

iex> "bare value" |> Result.or_else(fn
...>  :untagged, "bare value" -> :none
...> end)
:none
Link to this function

retag(result, new_tag)

Specs

retag(result_input(), new_tag) :: new_tag | {new_tag, any()}
when new_tag: atom()

If result is tagged :ok, replaces the tag with new_tag, returning a new tagged tuple.

Equivalent to tagged_retag(result, :ok, new_tag). See tagged_retag/3.

Examples

iex> :ok |> Result.retag(:none)
:none

iex> {:ok, "hello"} |> Result.retag(:error)
{:error, "hello"}

iex> {:ok, 1, 2} |> Result.retag(:error)
{:error, {1, 2}}

iex> {:error, 1, 2} |> Result.retag(:ok)
{:error, 1, 2}

iex> :ok |> Result.retag("string")
** (ArgumentError) Expected atom as new tag, got: "string".

iex> "bare value" |> Result.error_retag(:error)
"bare value"
Link to this function

tap(result, function)

Specs

If result is tagged with :ok, passes the wrapped value into the provided function and returns result unchanged. The return value of the function is ignored.

If result is not tagged with :ok, the provided function is not called.

Equivalent to tagged_tap(result, :ok, function). See tagged_tap/3.

Examples

iex> capture_io(fn ->
...>  assert :ok |> Result.tap(fn -> IO.write("hello") end) == :ok
...> end)
"hello"

iex> capture_io(fn ->
...>  input = {:ok, "in"}
...>  assert input |> Result.tap(fn "in" -> IO.write("hello") end) == input
...> end)
"hello"

iex> capture_io(fn ->
...>  input = {:ok, "one", "two"}
...>  function = fn {"one", "two"} -> IO.write("hello") end
...>  assert input |> Result.tap(function) == input
...> end)
"hello"

iex> capture_io(fn ->
...>  input = {:error, "reason"}
...>  assert input |> Result.tap(fn -> IO.write("hello") end) == input
...> end)
""
Link to this function

then(result, func_or_value)

Specs

then(result_input(), OkThen.Result.Private.func_or_value(out)) :: out
when out: any()

If result is tagged :ok, passes the wrapped value into func_or_value and returns the result. If a function is not provided, the argument at the same position is returned as-is.

If result is not tagged with the specified tag atom, result is returned as-is.

Use this function to pipe results into functions that return tagged tuples.

Be aware that no attempt is made to ensure the return value from the function is a tagged tuple. However, all functions are tolerant of untagged results, and on input will interpret them as an {:untagged, value} tuple.

Equivalent to tagged_then(result, :ok, func_or_value). See tagged_then/3.

Examples

iex> :ok |> Result.then({:ok, "hello"})
{:ok, "hello"}

iex> {:ok, 1} |> Result.then({:ok, "hello"})
{:ok, "hello"}

iex> :none |> Result.then({:ok, "hello"})
:none

iex> :ok |> Result.then(fn {} -> {:ok, "hello"} end)
{:ok, "hello"}

iex> {:ok, 1} |> Result.then(fn 1 -> {:ok, "hello"} end)
{:ok, "hello"}

iex> {:ok, 1, 2} |> Result.then(fn {1, 2} -> {:ok, "hello"} end)
{:ok, "hello"}

iex> {:ok, 1, 2} |> Result.then(fn 1, 2 -> {:ok, "hello"} end)
** (ArgumentError) Value-mapping function must have arity between 0 and 1.

iex> {:ok, 1, 2} |> Result.then(fn {1, 2} -> :ok end)
:ok

iex> :error |> Result.then(fn _ -> {:ok, "hello"} end)
:error

iex> {:error, 1} |> Result.then(fn _ -> {:ok, "hello"} end)
{:error, 1}

iex> {:error, 1, 2} |> Result.then(fn _ -> {:ok, "hello"} end)
{:error, 1, 2}

iex> :none |> Result.then(fn _ -> {:ok, "hello"} end)
:none

iex> :something_else |> Result.then(fn _ -> {:ok, "hello"} end)
:something_else

iex> "bare value" |> Result.then({:ok, "hello"})
"bare value"

iex> "bare value" |> Result.then(fn _ -> {:ok, "hello"} end)
"bare value"
Link to this function

unwrap!(result)

Specs

unwrap!(result_input()) :: any()

Same as unwrap_or_else/2, except raises ArgumentError if result is not tagged :ok.

Examples

iex> {:ok, "hello"} |> Result.unwrap!()
"hello"

iex> :ok |> Result.unwrap!()
{}

iex> :error |> Result.unwrap!()
** (ArgumentError) Result is not tagged ok: :error.

iex> {:error, "hello"} |> Result.unwrap!()
** (ArgumentError) Result is not tagged ok: {:error, "hello"}.

iex> :none |> Result.unwrap!()
** (ArgumentError) Result is not tagged ok: :none.

iex> "hello" |> Result.unwrap!()
** (ArgumentError) Result is not tagged ok: "hello".
Link to this function

unwrap_or_else(result, func_or_value)

Specs

Returns the wrapped value if result is tagged :ok. Otherwise, passes the tag and wrapped value into the provided function and returns the result. If the function has arity 1, then only the wrapped value is passed in. An arity-0 function is also accepted. If func_or_value is not a function, then it is used directly as the new value.

See also tagged_or_else/3.

Equivalent to tagged_unwrap_or_else(result, :ok, default). See tagged_unwrap_or_else/3.

Examples

iex> {:ok, "hello"} |> Result.unwrap_or_else("default")
"hello"

iex> :ok |> Result.unwrap_or_else("default")
{}

iex> :error |> Result.unwrap_or_else("default")
"default"

iex> {:error, "hello"} |> Result.unwrap_or_else("default")
"default"

iex> {:error, "hello"}
...> |> Result.unwrap_or_else(fn
...>   :error, "hello" -> "default"
...> end)
"default"

iex> {:error, "hello"}
...> |> Result.unwrap_or_else(fn
...>   "hello" -> "default"
...> end)
"default"

iex> {:error, "hello"} |> Result.unwrap_or_else(fn -> "default" end)
"default"

iex> :none |> Result.unwrap_or_else("default")
"default"

iex> "hello" |> Result.unwrap_or_else("default")
"default"

Link to this section Functions (:error)

Link to this function

error_consume(result, function \\ &(&1))

Specs

error_consume(result_input(), (any() -> any())) :: result_input() | :none

If result is tagged with :error, passes the wrapped value into the provided function (if provided) and returns :none.

If result is not tagged with :error, result is returned as-is.

Equivalent to tagged_consume(result, :error, func_or_value). See tagged_consume/3.

Examples

iex> :error |> Result.error_consume()
:none

iex> :error |> Result.error_consume(fn -> "hello" end)
:none

iex> :error |> Result.error_consume(fn {} -> "hello" end)
:none

iex> {:error, 1} |> Result.error_consume(fn 1 -> "hello" end)
:none

iex> {:error, 1, 2} |> Result.error_consume(fn 1, 2 -> "hello" end)
** (ArgumentError) Value-mapping function must have arity between 0 and 1.

iex> {:error, 1, 2} |> Result.error_consume(fn {1, 2} -> "hello" end)
:none

iex> :ok |> Result.error_consume(fn {} -> "hello" end)
:ok

iex> {:ok, 1} |> Result.error_consume(fn 1 -> "hello" end)
{:ok, 1}

iex> "bare value" |> Result.error_consume()
"bare value"
Link to this function

error_filter(result, func_or_value)

Specs

If result is tagged :error, passes the wrapped value into the provided function. If func_or_value returns a truthy value, result is returned unchanged. Otherwise, returns :none. If func_or_value is not a function, then it is used directly as the check value.

Equivalent to tagged_filter(result, :error, func_or_value). See tagged_filter/3.

Examples

iex> {:error, "hello"} |> Result.error_filter(&String.length(&1) == 5)
{:error, "hello"}

iex> {:error, "hello"} |> Result.error_filter(&String.length(&1) == 0)
:none

iex> {:error, "hello"} |> Result.error_filter(fn -> true end)
{:error, "hello"}

iex> {:error, "hello"} |> Result.error_filter(fn -> false end)
:none

iex> {:error, "hello"} |> Result.error_filter(true)
{:error, "hello"}

iex> {:error, "hello"} |> Result.error_filter(false)
:none

iex> :some |> Result.error_filter(&String.length(&1) == 0)
:some

iex> :ok |> Result.error_filter(&String.length(&1) == 0)
:ok

iex> nil |> Result.error_filter(&String.length(&1) == 0)
nil
Link to this function

error_map(result, func_or_value)

Specs

error_map(t, OkThen.Result.Private.func_or_value(out)) ::
  t | :error | error(out)
when t: result_input(), out: any()

If result is tagged :error, transforms the wrapped value by passing it into the provided mapping function, and replacing it with the returned value. If func_or_value is not a function, then it is used directly as the new value.

If the new value would be nil, then :none is returned as the result instead. Consider piping into |> none_then({:error, nil}) if you really want {:error, nil}. See none_then/2.

If result is not tagged :error, result is returned as-is.

Equivalent to tagged_map(result, :error, func_or_value). See tagged_map/3.

Examples

iex> :error |> Result.error_map("hello")
{:error, "hello"}

iex> {:error, 1} |> Result.error_map("hello")
{:error, "hello"}

iex> {:error, 1} |> Result.error_map(nil)
:none

iex> :none |> Result.error_map("hello")
:none

iex> :error |> Result.error_map(fn {} -> "hello" end)
{:error, "hello"}

iex> {:error, 1} |> Result.error_map(fn 1 -> "hello" end)
{:error, "hello"}

iex> {:error, 1, 2} |> Result.error_map(fn {1, 2} -> "hello" end)
{:error, "hello"}

iex> {:error, 1, 2} |> Result.error_map(fn 1, 2 -> "hello" end)
** (ArgumentError) Value-mapping function must have arity between 0 and 1.

iex> {:error, 1, 2} |> Result.error_map(fn {1, 2} -> {} end)
:error

iex> :ok |> Result.error_map(fn _ -> "hello" end)
:ok

iex> {:ok, 1} |> Result.error_map(fn _ -> "hello" end)
{:ok, 1}

iex> {:ok, 1, 2} |> Result.error_map(fn _ -> "hello" end)
{:ok, 1, 2}

iex> :none |> Result.error_map(fn _ -> "hello" end)
:none

iex> :something_else |> Result.error_map(fn _ -> "hello" end)
:something_else

iex> "bare value" |> Result.error_map(fn _ -> "hello" end)
"bare value"

iex> "bare value" |> Result.error_map("hello")
"bare value"
Link to this function

error_or_else(result, func_or_value)

Specs

error_or_else(result_input(), OkThen.Result.Private.func_or_value(atom(), out)) ::
  out
when out: any()

If result is not tagged with :error, passes the tag and wrapped value into the provided function and returns the result. If the function has arity 1, then only the wrapped value is passed in. If func_or_value is not a function, then it is used directly as the new value.

If result is tagged with :error, result is returned as-is.

Use this function in a pipeline to branch the happy path into another function, or as a kind of case expression to handle multiple types of result without the boilerplate of copying through an error result.

Be aware that no attempt is made to ensure the return value from the function is a tagged tuple. However, all functions are tolerant of untagged results, and on input will interpret them as an {:untagged, value} tuple.

Equivalent to tagged_or_else(result, :error, func_or_value). See tagged_or_else/3.

Examples

iex> :ok
...> |> Result.error_or_else(fn
...>   :ok, {} -> {:ok, "hello"}
...> end)
{:ok, "hello"}

iex> {:ok, 1}
...> |> Result.error_or_else(fn
...>   :ok, 1 -> {:ok, "hello"}
...> end)
{:ok, "hello"}

iex> {:ok, 1, 2}
...> |> Result.error_or_else(fn
...>   :ok, {1, 2} -> {:ok, "matched ok"}
...>   :none, {} -> {:ok, "matched none"}
...> end)
{:ok, "matched ok"}

iex> :none
...> |> Result.error_or_else(fn
...>   :ok, {1, 2} -> {:ok, "matched ok"}
...>   :none, {} -> {:ok, "matched none"}
...> end)
{:ok, "matched none"}

iex> {:error, "just error"}
...> |> Result.error_or_else(fn
...>   :ok, {1, 2} -> {:ok, "matched ok"}
...>   :none, {} -> {:ok, "matched none"}
...> end)
{:error, "just error"}

iex> {:ok, 1, 2}
...> |> Result.error_or_else(fn
...>   {1, 2} -> {:ok, "matched two terms"}
...>   1 -> {:ok, "matched one term"}
...>   {} -> {:ok, "matched no terms"}
...> end)
{:ok, "matched two terms"}

iex> {:ok, 1}
...> |> Result.error_or_else(fn
...>   {1, 2} -> {:ok, "matched two terms"}
...>   1 -> {:ok, "matched one term"}
...>   {} -> {:ok, "matched no terms"}
...> end)
{:ok, "matched one term"}

iex> :ok
...> |> Result.error_or_else(fn
...>   {1, 2} -> {:ok, "matched two terms"}
...>   1 -> {:ok, "matched one term"}
...>   {} -> {:ok, "matched no terms"}
...> end)
{:ok, "matched no terms"}

iex> :none
...> |> Result.error_or_else(fn
...>   {1, 2} -> {:ok, "matched two terms"}
...>   1 -> {:ok, "matched one term"}
...>   {} -> {:ok, "matched no terms"}
...> end)
{:ok, "matched no terms"}

iex> :ok |> Result.error_or_else({:ok, "hello"})
{:ok, "hello"}

iex> {:ok, 1} |> Result.error_or_else({:ok, "hello"})
{:ok, "hello"}

iex> {:ok, 1} |> Result.error_or_else("bare value")
"bare value"

iex> "bare value" |> Result.error_or_else(fn _ -> :none end)
:none

iex> "bare value" |> Result.error_or_else(fn
...>  :untagged, "bare value" -> :none
...> end)
:none
Link to this function

error_retag(result, new_tag)

Specs

error_retag(result_input(), new_tag) :: new_tag | {new_tag, any()}
when new_tag: atom()

If result is tagged :error, replaces the tag with new_tag, returning a new tagged tuple.

Equivalent to tagged_retag(result, :error, new_tag). See tagged_retag/3.

Examples

iex> :error |> Result.error_retag(:none)
:none

iex> {:error, "hello"} |> Result.error_retag(:ok)
{:ok, "hello"}

iex> {:error, 1, 2} |> Result.error_retag(:ok)
{:ok, {1, 2}}

iex> {:ok, 1, 2} |> Result.error_retag(:error)
{:ok, 1, 2}

iex> :error |> Result.error_retag("string")
** (ArgumentError) Expected atom as new tag, got: "string".

iex> "bare value" |> Result.error_retag(:ok)
"bare value"
Link to this function

error_tap(result, function)

Specs

If result is tagged with :error, passes the wrapped value into the provided function and returns result unchanged. The return value of the function is ignored.

If result is not tagged with :error, the provided function is not called.

Equivalent to tagged_tap(result, :error, function). See tagged_tap/3.

Examples

iex> capture_io(fn ->
...>  assert :error |> Result.error_tap(fn -> IO.write("hello") end) == :error
...> end)
"hello"

iex> capture_io(fn ->
...>  input = {:error, "reason"}
...>  assert input |> Result.error_tap(fn "reason" -> IO.write("hello") end) == input
...> end)
"hello"

iex> capture_io(fn ->
...>  input = {:error, "one", "two"}
...>  function = fn {"one", "two"} -> IO.write("hello") end
...>  assert input |> Result.error_tap(function) == input
...> end)
"hello"

iex> capture_io(fn ->
...>  input = {:ok, "in"}
...>  assert input |> Result.error_tap(fn -> IO.write("hello") end) == input
...> end)
""
Link to this function

error_then(result, func_or_value)

Specs

error_then(result_input(), OkThen.Result.Private.func_or_value(out)) :: out
when out: any()

If result is tagged :error, passes the wrapped value into func_or_value and returns the result. If a function is not provided, the argument at the same position is returned as-is.

If result is not tagged with the specified tag atom, result is returned as-is.

Use this function to pipe results into functions that return tagged tuples.

Be aware that no attempt is made to ensure the return value from the function is a tagged tuple. However, all functions are tolerant of untagged results, and on input will interpret them as an {:untagged, value} tuple.

Equivalent to tagged_then(result, :error, func_or_value). See tagged_then/3.

Examples

iex> :error |> Result.error_then({:ok, "hello"})
{:ok, "hello"}

iex> {:error, 1} |> Result.error_then({:ok, "hello"})
{:ok, "hello"}

iex> :none |> Result.error_then({:ok, "hello"})
:none

iex> :error |> Result.error_then(fn -> {:ok, "hello"} end)
{:ok, "hello"}

iex> :error |> Result.error_then(fn {} -> {:ok, "hello"} end)
{:ok, "hello"}

iex> {:error, 1} |> Result.error_then(fn 1 -> {:ok, "hello"} end)
{:ok, "hello"}

iex> {:error, 1, 2} |> Result.error_then(fn {1, 2} -> {:ok, "hello"} end)
{:ok, "hello"}

iex> {:error, 1, 2} |> Result.error_then(fn 1, 2 -> {:ok, "hello"} end)
** (ArgumentError) Value-mapping function must have arity between 0 and 1.

iex> {:error, 1, 2} |> Result.error_then(fn {1, 2} -> :ok end)
:ok

iex> :ok |> Result.error_then(fn _ -> {:ok, "hello"} end)
:ok

iex> {:ok, 1} |> Result.error_then(fn _ -> {:ok, "hello"} end)
{:ok, 1}

iex> {:ok, 1, 2} |> Result.error_then(fn _ -> {:ok, "hello"} end)
{:ok, 1, 2}

iex> :none |> Result.error_then(fn _ -> {:ok, "hello"} end)
:none

iex> :something_else |> Result.error_then(fn _ -> {:ok, "hello"} end)
:something_else

iex> "bare value" |> Result.error_then({:ok, "hello"})
"bare value"

iex> "bare value" |> Result.error_then(fn _ -> {:ok, "hello"} end)
"bare value"
Link to this function

error_unwrap!(result)

Specs

error_unwrap!(result_input()) :: any()

Same as error_unwrap_or_else/2, except raises ArgumentError if result is not tagged :error.

Examples

iex> {:error, "hello"} |> Result.error_unwrap!()
"hello"

iex> :error |> Result.error_unwrap!()
{}

iex> :ok |> Result.error_unwrap!()
** (ArgumentError) Result is not tagged error: :ok.

iex> {:ok, "hello"} |> Result.error_unwrap!()
** (ArgumentError) Result is not tagged error: {:ok, "hello"}.

iex> :none |> Result.error_unwrap!()
** (ArgumentError) Result is not tagged error: :none.

iex> "hello" |> Result.error_unwrap!()
** (ArgumentError) Result is not tagged error: "hello".
Link to this function

error_unwrap_or_else(result, func_or_value)

Specs

error_unwrap_or_else(result_input(), OkThen.Result.Private.func_or_value(any())) ::
  any()

Returns the wrapped value if result is tagged :error. Otherwise, passes the tag and wrapped value into the provided function and returns the result. If the function has arity 1, then only the wrapped value is passed in. An arity-0 function is also accepted. If func_or_value is not a function, then it is used directly as the new value.

See also tagged_or_else/3.

Equivalent to tagged_unwrap_or_else(result, :error, default). See tagged_unwrap_or_else/3.

Examples

iex> {:error, "hello"} |> Result.error_unwrap_or_else("default")
"hello"

iex> :error |> Result.error_unwrap_or_else("default")
{}

iex> :ok |> Result.error_unwrap_or_else("default")
"default"

iex> {:ok, "hello"} |> Result.error_unwrap_or_else("default")
"default"

iex> {:ok, "hello"}
...> |> Result.error_unwrap_or_else(fn
...>   :ok, "hello" -> "default"
...> end)
"default"

iex> {:ok, "hello"}
...> |> Result.error_unwrap_or_else(fn
...>   "hello" -> "default"
...> end)
"default"

iex> {:error, "hello"} |> Result.tagged_unwrap_or_else(:ok, fn -> "default" end)
"default"

iex> :none |> Result.error_unwrap_or_else("default")
"default"

iex> "hello" |> Result.error_unwrap_or_else("default")
"default"

Link to this section Functions (:none)

Link to this function

default(result, func_or_value)

Specs

default(input, (() -> out) | out) :: input | :ok | ok(out)
when input: result_input(), out: any()

If result is tagged :none, returns func_or_value wrapped as an :ok result. Otherwise, returns result. If func_or_value is a function, the returned value is used as the new value.

If the new value is nil, then the result will remain :none. Consider using none_then/2 if you don't want this behaviour.

If result is not tagged :none, result is returned as-is.

Equivalent to default_as(result, :ok, func_or_value). See default_as/3.

Examples

iex> :none |> Result.default("hello")
{:ok, "hello"}

iex> :none |> Result.default({})
:ok

iex> :none |> Result.default(nil)
:none

iex> :none |> Result.default(fn -> 1 end)
{:ok, 1}

iex> :none |> Result.default(fn {} -> 1 end)
{:ok, 1}

iex> {:none, 1} |> Result.default(& &1)
{:ok, 1}

iex> :ok |> Result.default("hello")
:ok

iex> {:ok, 1} |> Result.default("hello")
{:ok, 1}

iex> {:ok, 1, 2} |> Result.default("hello")
{:ok, 1, 2}

iex> {:anything, 1} |> Result.default("hello")
{:anything, 1}

iex> "bare value" |> Result.default("hello")
"bare value"
Link to this function

default!(result, func_or_value)

Same as default/2, except raises ArgumentError if func_or_value returns nil.

Examples

iex> :none |> Result.default!("hello")
{:ok, "hello"}

iex> :none |> Result.default!({})
:ok

iex> :none |> Result.default!(nil)
** (ArgumentError) Value is nil.

iex> :none |> Result.default!(fn -> nil end)
** (ArgumentError) Value is nil.
Link to this function

default_as(result, tag, func_or_value)

Specs

default_as(input, tag, (() -> out) | out) :: input | tag | {tag, out}
when input: result_input(), tag: atom(), out: any()

If result is tagged :none, returns func_or_value wrapped as a result with the given tag. Otherwise, returns result. If func_or_value is a function, the returned value is used as the new value.

If the new value is nil, then the result will remain :none. Consider using none_then/2 if you don't want this behaviour.

If result is not tagged :none, result is returned as-is.

Examples

iex> :none |> Result.default_as(:ok, "hello")
{:ok, "hello"}

iex> :none |> Result.default_as(:error, {})
:error

iex> :none |> Result.default_as(:something, nil)
:none

iex> :none |> Result.default_as(:ok, fn -> 1 end)
{:ok, 1}

iex> :none |> Result.default_as(:ok, fn {} -> 1 end)
{:ok, 1}

iex> {:none, 1} |> Result.default_as(:ok, & &1)
{:ok, 1}

iex> :ok |> Result.default_as(:ok, "hello")
:ok

iex> {:ok, 1} |> Result.default_as(:ok, "hello")
{:ok, 1}

iex> {:ok, 1, 2} |> Result.default_as(:ok, "hello")
{:ok, 1, 2}

iex> {:anything, 1} |> Result.default_as(:ok, "hello")
{:anything, 1}

iex> "bare value" |> Result.default_as(:ok, "hello")
"bare value"
Link to this function

default_as!(result, tag, func_or_value)

Specs

default_as!(input, tag, (() -> out) | out) :: input | tag | {tag, out}
when input: result_input(), tag: atom(), out: any()

Same as default_as/3, except raises ArgumentError if func_or_value returns nil.

Examples

iex> :none |> Result.default_as!(:ok, "hello")
{:ok, "hello"}

iex> :none |> Result.default_as!(:ok, {})
:ok

iex> :none |> Result.default_as!(:ok, nil)
** (ArgumentError) Value is nil.

iex> :none |> Result.default_as!(:ok, fn -> nil end)
** (ArgumentError) Value is nil.
Link to this function

default_error(result, func_or_value)

Specs

default_error(input, (() -> out) | out) :: input | :error | error(out)
when input: result_input(), out: any()

If result is tagged :none, returns func_or_value wrapped as an :error result. Otherwise, returns result. If func_or_value is a function, the returned value is used as the new value.

If the new value is nil, then the result will remain :none. Consider using none_then/2 if you don't want this behaviour.

If result is not tagged :none, result is returned as-is.

Equivalent to default_as(result, :error, func_or_value). See default_as/3.

Examples

iex> :none |> Result.default_error("hello")
{:error, "hello"}

iex> :none |> Result.default_error({})
:error

iex> :none |> Result.default_error(nil)
:none

iex> :none |> Result.default_error(fn -> 1 end)
{:error, 1}

iex> :none |> Result.default_error(fn {} -> 1 end)
{:error, 1}

iex> {:none, 1} |> Result.default_error(& &1)
{:error, 1}

iex> :error |> Result.default_error("hello")
:error

iex> {:error, 1} |> Result.default_error("hello")
{:error, 1}

iex> {:error, 1, 2} |> Result.default_error("hello")
{:error, 1, 2}

iex> {:anything, 1} |> Result.default_error("hello")
{:anything, 1}

iex> "bare value" |> Result.default_error("hello")
"bare value"
Link to this function

default_error!(result, func_or_value)

Same as default_error/2, except raises ArgumentError if func_or_value returns nil.

Examples

iex> :none |> Result.default_error!("hello")
{:error, "hello"}

iex> :none |> Result.default_error!({})
:error

iex> :none |> Result.default_error!(nil)
** (ArgumentError) Value is nil.

iex> :none |> Result.default_error!(fn -> nil end)
** (ArgumentError) Value is nil.
Link to this function

none_retag(result, new_tag)

Specs

none_retag(result_input(), new_tag) :: new_tag | {new_tag, any()}
when new_tag: atom()

If result is tagged :none, replaces the tag with new_tag.

Equivalent to tagged_retag(result, :none, new_tag). See tagged_retag/3.

Examples

iex> :none |> Result.none_retag(:ok)
:ok

iex> :error |> Result.none_retag(:ok)
:error

iex> :ok |> Result.none_retag(:ok)
:ok

iex> :none |> Result.none_retag("string")
** (ArgumentError) Expected atom as new tag, got: "string".

iex> "bare value" |> Result.none_retag(:error)
"bare value"
Link to this function

none_then(result, func_or_value)

Specs

none_then(result_input(), OkThen.Result.Private.func_or_value(out)) :: out
when out: any()

If result is tagged :none, calls func_or_value and returns the result. If func_or_value is not a function, then it is returned as-is.

If result is not tagged with the specified tag atom, result is returned as-is.

Use this function to pipe results into functions that return tagged tuples.

Be aware that no attempt is made to ensure the return value from the function is a tagged tuple. However, all functions are tolerant of untagged results, and on input will interpret them as an {:untagged, value} tuple.

Equivalent to tagged_then(result, :none, func_or_value). See tagged_then/3.

A note about unwrapped nils

If result is nil, it is intprereted as :none. This may be slightly unintuitive, so if you're curious, this is the reason:

Untagged results are internally wrapped as an {:untagged, any()} using Result.from_as(value, :untagged), and if value is nil, the return value of Result.from_as/2 will always be :none.

Examples

iex> :none |> Result.none_then(:ok)
:ok

iex> :none |> Result.none_then(fn {} -> {:ok, "hello"} end)
{:ok, "hello"}

iex> :error |> Result.none_then(fn -> {:ok, "hello"} end)
:error

iex> :error |> Result.none_then(fn _ -> {:ok, "hello"} end)
:error

iex> {:error, 1} |> Result.none_then(fn _ -> {:ok, "hello"} end)
{:error, 1}

iex> {:error, 1, 2} |> Result.none_then(fn _ -> {:ok, "hello"} end)
{:error, 1, 2}

iex> :something_else |> Result.none_then(fn _ -> {:ok, "hello"} end)
:something_else

iex> "bare value" |> Result.none_then(:ok)
"bare value"

iex> "bare value" |> Result.none_then(fn _ -> :ok end)
"bare value"

iex> nil |> Result.none_then(:ok)
:ok

Link to this section Functions (any tag)

Link to this function

tagged_consume(result, tag, function \\ &(&1))

Specs

tagged_consume(result_input(), atom(), (any() -> any())) ::
  result_input() | :none

If result is tagged with the specified tag atom, passes the wrapped value into the provided function (if provided) and returns :none.

If result is not tagged with the specified tag atom, result is returned as-is.

Examples

iex> :ok |> Result.tagged_consume(:ok)
:none

iex> :error |> Result.tagged_consume(:error, fn -> "hello" end)
:none

iex> :error |> Result.tagged_consume(:error, fn {} -> "hello" end)
:none

iex> {:some, 1} |> Result.tagged_consume(:some, fn 1 -> "hello" end)
:none

iex> {:ok, 1, 2} |> Result.tagged_consume(:ok, fn 1, 2 -> "hello" end)
** (ArgumentError) Value-mapping function must have arity between 0 and 1.

iex> {:ok, 1, 2} |> Result.tagged_consume(:ok, fn {1, 2} -> "hello" end)
:none

iex> :error |> Result.tagged_consume(:ok, fn {} -> "hello" end)
:error

iex> {:error, 1} |> Result.tagged_consume(:ok, fn 1 -> "hello" end)
{:error, 1}

iex> "bare value" |> Result.tagged_consume(:ok)
"bare value"
Link to this function

tagged_filter(result, tag, func_or_value)

Specs

If result is tagged with the specified tag atom, passes the wrapped value into the provided function. If func_or_value returns a truthy value, result is returned unchanged. Otherwise, returns :none. If func_or_value is not a function, then it is used directly as the check value.

Examples

iex> {:ok, "hello"} |> Result.tagged_filter(:ok, &String.length(&1) == 5)
{:ok, "hello"}

iex> {:ok, "hello"} |> Result.tagged_filter(:ok, &String.length(&1) == 0)
:none

iex> {:ok, "hello"} |> Result.tagged_filter(:ok, fn -> true end)
{:ok, "hello"}

iex> {:ok, "hello"} |> Result.tagged_filter(:ok, fn -> false end)
:none

iex> {:ok, "hello"} |> Result.tagged_filter(:ok, true)
{:ok, "hello"}

iex> {:ok, "hello"} |> Result.tagged_filter(:ok, false)
:none

iex> :some |> Result.tagged_filter(:ok, &String.length(&1) == 0)
:some

iex> :error |> Result.tagged_filter(:ok, &String.length(&1) == 0)
:error

iex> nil |> Result.tagged_filter(:ok, &String.length(&1) == 0)
nil
Link to this function

tagged_map(result, tag, func_or_value)

Specs

tagged_map(t, tag, OkThen.Result.Private.func_or_value(out)) ::
  t | tag | {tag, out}
when t: result_input(), tag: atom(), out: any()

If result is tagged with the specified tag atom, transforms the wrapped value by passing it into the provided mapping function, and replacing it with the returned value. If a function is not provided, the argument at the same position is used as the new value.

If the new value would be nil, then :none is returned as the result instead. Consider piping into |> none_then({tag, nil}) if you really want {tag, nil}. See none_then/2.

If result is not tagged with the specified tag atom, result is returned as-is.

Examples

iex> :ok |> Result.tagged_map(:ok, "hello")
{:ok, "hello"}

iex> {:ok, 1} |> Result.tagged_map(:ok, "hello")
{:ok, "hello"}

iex> {:ok, 1} |> Result.tagged_map(:ok, nil)
:none

iex> :none |> Result.tagged_map(:ok, "hello")
:none

iex> :ok |> Result.tagged_map(:ok, fn -> "hello" end)
{:ok, "hello"}

iex> :ok |> Result.tagged_map(:ok, fn {} -> "hello" end)
{:ok, "hello"}

iex> {:bla, 1} |> Result.tagged_map(:bla, fn 1 -> "hello" end)
{:bla, "hello"}

iex> {:some, 1, 2} |> Result.tagged_map(:some, fn {1, 2} -> "hello" end)
{:some, "hello"}

iex> {:ok, 1, 2} |> Result.tagged_map(:ok, fn 1, 2 -> "hello" end)
** (ArgumentError) Value-mapping function must have arity between 0 and 1.

iex> {:ok, 1, 2} |> Result.tagged_map(:ok, fn {1, 2} -> {} end)
:ok

iex> :error |> Result.tagged_map(:ok, fn _ -> "hello" end)
:error

iex> {:error, 1} |> Result.tagged_map(:ok, fn _ -> "hello" end)
{:error, 1}

iex> {:error, 1, 2} |> Result.tagged_map(:ok, fn _ -> "hello" end)
{:error, 1, 2}

iex> :none |> Result.tagged_map(:ok, fn _ -> "hello" end)
:none

iex> :something_else |> Result.tagged_map(:ok, fn _ -> "hello" end)
:something_else

iex> "bare value" |> Result.tagged_map(:ok, fn _ -> "hello" end)
"bare value"

iex> "bare value" |> Result.tagged_map(:untagged, fn _ -> "hello" end)
{:untagged, "hello"}

iex> "bare value" |> Result.tagged_map(:ok, "hello")
"bare value"

iex> "bare value" |> Result.tagged_map(:untagged, "hello")
{:untagged, "hello"}
Link to this function

tagged_or_else(result, tag, func_or_value)

Specs

tagged_or_else(
  result_input(),
  atom(),
  OkThen.Result.Private.func_or_value(atom(), out)
) :: out
when out: any()

If result is not tagged with the specified tag atom, passes the tag and wrapped value into the provided function and returns the result. If the function has arity 1, then only the wrapped value is passed in. An arity-0 function is also accepted. If func_or_value is not a function, then it is used directly as the new value.

If result is tagged with the specified tag atom, result is returned as-is.

Use this function in a pipeline to branch away from the happy path, or as a kind of case expression to handle multiple types of result without the boilerplate of copying through a successful result.

Be aware that no attempt is made to ensure the return value from the function is a tagged tuple. However, all functions are tolerant of untagged results, and on input will interpret them as an {:untagged, value} tuple.

Examples

iex> :error
...> |> Result.tagged_or_else(:ok, fn
...>   :error, {} -> {:ok, "hello"}
...> end)
{:ok, "hello"}

iex> {:error, 1}
...> |> Result.tagged_or_else(:ok, fn
...>   :error, 1 -> {:ok, "hello"}
...> end)
{:ok, "hello"}

iex> {:error, 1, 2}
...> |> Result.tagged_or_else(:ok, fn
...>   :error, {1, 2} -> {:ok, "matched error"}
...>   :none, {} -> {:ok, "matched none"}
...> end)
{:ok, "matched error"}

iex> :none
...> |> Result.tagged_or_else(:ok, fn
...>   :error, {1, 2} -> {:ok, "matched error"}
...>   :none, {} -> {:ok, "matched none"}
...> end)
{:ok, "matched none"}

iex> {:ok, "just ok"}
...> |> Result.tagged_or_else(:ok, fn
...>   :error, {1, 2} -> {:ok, "matched error"}
...>   :none, {} -> {:ok, "matched none"}
...> end)
{:ok, "just ok"}

iex> {:error, 1, 2}
...> |> Result.tagged_or_else(:ok, fn
...>   {1, 2} -> {:ok, "matched two terms"}
...>   1 -> {:ok, "matched one term"}
...>   {} -> {:ok, "matched no terms"}
...> end)
{:ok, "matched two terms"}

iex> {:error, 1}
...> |> Result.tagged_or_else(:ok, fn
...>   {1, 2} -> {:ok, "matched two terms"}
...>   1 -> {:ok, "matched one term"}
...>   {} -> {:ok, "matched no terms"}
...> end)
{:ok, "matched one term"}

iex> :error
...> |> Result.tagged_or_else(:ok, fn
...>   {1, 2} -> {:ok, "matched two terms"}
...>   1 -> {:ok, "matched one term"}
...>   {} -> {:ok, "matched no terms"}
...> end)
{:ok, "matched no terms"}

iex> :none
...> |> Result.tagged_or_else(:ok, fn
...>   {1, 2} -> {:ok, "matched two terms"}
...>   1 -> {:ok, "matched one term"}
...>   {} -> {:ok, "matched no terms"}
...> end)
{:ok, "matched no terms"}

iex> :none
...> |> Result.tagged_or_else(:ok, fn -> {:ok, "catch-all value"} end)
{:ok, "catch-all value"}

iex> :error |> Result.tagged_or_else(:ok, {:ok, "hello"})
{:ok, "hello"}

iex> {:error, 1} |> Result.tagged_or_else(:ok, {:ok, "hello"})
{:ok, "hello"}

iex> {:error, 1} |> Result.tagged_or_else(:ok, "bare value")
"bare value"

iex> "bare value" |> Result.tagged_or_else(:ok, fn _ -> :none end)
:none

iex> "bare value" |> Result.tagged_or_else(:ok, fn
...>  :untagged, "bare value" -> :none
...> end)
:none

iex> "bare value" |> Result.tagged_or_else(:untagged, fn _ -> :none end)
"bare value"
Link to this function

tagged_retag(result, tag, new_tag)

Specs

tagged_retag(result_input(), atom(), new_tag) :: new_tag | {new_tag, any()}
when new_tag: atom()

If result is tagged with the specified tag atom, replaces the tag with new_tag, returning a new tagged tuple.

Examples

iex> :ok |> Result.tagged_retag(:ok, :none)
:none

iex> {:ok, "hello"} |> Result.tagged_retag(:ok, :error)
{:error, "hello"}

iex> {:error, 1, 2} |> Result.tagged_retag(:error, :ok)
{:ok, {1, 2}}

iex> {:ok, 1, 2} |> Result.tagged_retag(:error, :ok)
{:ok, 1, 2}

iex> :ok |> Result.tagged_retag(:ok, "string")
** (ArgumentError) Expected atom as new tag, got: "string".

iex> "bare value" |> Result.tagged_retag(:ok, :error)
"bare value"

iex> "bare value" |> Result.tagged_retag(:untagged, :error)
{:error, "bare value"}
Link to this function

tagged_tap(result, tag, function)

Specs

If result is tagged with the specified tag atom, passes the wrapped value into the provided function and returns result unchanged. The return value of the function is ignored.

If result is not tagged with the specified tag atom, the provided function is not called.

Examples

iex> capture_io(fn ->
...>  assert :ok |> Result.tagged_tap(:ok, fn -> IO.write("hello") end) == :ok
...> end)
"hello"

iex> capture_io(fn ->
...>  input = {:ok, "in"}
...>  assert input |> Result.tagged_tap(:ok, fn "in" -> IO.write("hello") end) == input
...> end)
"hello"

iex> capture_io(fn ->
...>  input = {:ok, "one", "two"}
...>  function = fn {"one", "two"} -> IO.write("hello") end
...>  assert input |> Result.tagged_tap(:ok, function) == input
...> end)
"hello"

iex> capture_io(fn ->
...>  input = {:error, "reason"}
...>  assert input |> Result.tagged_tap(:ok, fn -> IO.write("hello") end) == input
...> end)
""
Link to this function

tagged_then(result, tag, func_or_value)

Specs

tagged_then(result_input(), atom(), OkThen.Result.Private.func_or_value(out)) ::
  out
when out: any()

If result is tagged with the specified tag atom, passes the wrapped value into the provided function and returns the result. If func_or_value is not a function, then it is returned as-is.

If result is not tagged with the specified tag atom, result is returned as-is.

Use this function to pipe results into functions that return tagged tuples.

Be aware that no attempt is made to ensure the return value from the function is a tagged tuple. However, all functions are tolerant of untagged results, and on input will interpret them as an {:untagged, value} tuple.

A note about unwrapped nils

If result is nil, it is intprereted as :none. This may be slightly unintuitive, so if you're curious, this is the reason:

Untagged results are internally wrapped as an {:untagged, any()} using Result.from_as(value, :untagged), and if value is nil, the return value of Result.from_as/2 will always be :none.

Examples

iex> :ok |> Result.tagged_then(:ok, {:ok, "hello"})
{:ok, "hello"}

iex> {:ok, 1} |> Result.tagged_then(:ok, {:ok, "hello"})
{:ok, "hello"}

iex> {:ok, 1} |> Result.tagged_then(:ok, "bare value")
"bare value"

iex> :none |> Result.tagged_then(:ok, {:ok, "hello"})
:none

iex> :ok |> Result.tagged_then(:ok, fn -> "bare value" end)
"bare value"

iex> :ok |> Result.tagged_then(:ok, fn {} -> "bare value" end)
"bare value"

iex> :ok |> Result.tagged_then(:ok, fn {} -> {:ok, "hello"} end)
{:ok, "hello"}

iex> {:ok, 1} |> Result.tagged_then(:ok, fn 1 -> {:ok, "hello"} end)
{:ok, "hello"}

iex> {:ok, 1, 2} |> Result.tagged_then(:ok, fn {1, 2} -> {:ok, "hello"} end)
{:ok, "hello"}

iex> {:ok, 1, 2} |> Result.tagged_then(:ok, fn 1, 2 -> {:ok, "hello"} end)
** (ArgumentError) Value-mapping function must have arity between 0 and 1.

iex> {:ok, 1, 2} |> Result.tagged_then(:ok, fn {1, 2} -> {:ok, {}} end)
{:ok, {}}

iex> :error |> Result.tagged_then(:ok, fn _ -> {:ok, "hello"} end)
:error

iex> {:error, 1} |> Result.tagged_then(:ok, fn _ -> {:ok, "hello"} end)
{:error, 1}

iex> {:error, 1, 2} |> Result.tagged_then(:ok, fn _ -> {:ok, "hello"} end)
{:error, 1, 2}

iex> :none |> Result.tagged_then(:ok, fn _ -> {:ok, "hello"} end)
:none

iex> :something_else |> Result.tagged_then(:ok, fn _ -> {:ok, "hello"} end)
:something_else

iex> "bare value" |> Result.tagged_then(:ok, fn _ -> :none end)
"bare value"

iex> "bare value" |> Result.tagged_then(:untagged, fn _ -> :none end)
:none

iex> "bare value" |> Result.tagged_then(:ok, :none)
"bare value"

iex> "bare value" |> Result.tagged_then(:untagged, :none)
:none

iex> nil |> Result.tagged_then(:none, :ok)
:ok
Link to this function

tagged_unwrap!(result, tag)

Specs

tagged_unwrap!(result_input(), atom()) :: any()

Same as tagged_unwrap_or_else/3, except raises ArgumentError if result is not tagged with the specified tag atom.

Examples

iex> {:ok, "hello"} |> Result.tagged_unwrap!(:ok)
"hello"

iex> :some |> Result.tagged_unwrap!(:some)
{}

iex> :error |> Result.tagged_unwrap!(:ok)
** (ArgumentError) Result is not tagged ok: :error.

iex> {:ok, "hello"} |> Result.tagged_unwrap!(:error)
** (ArgumentError) Result is not tagged error: {:ok, "hello"}.

iex> :none |> Result.tagged_unwrap!(:ok)
** (ArgumentError) Result is not tagged ok: :none.

iex> "hello" |> Result.tagged_unwrap!(:untagged)
"hello"

iex> "hello" |> Result.tagged_unwrap!(:ok)
** (ArgumentError) Result is not tagged ok: "hello".
Link to this function

tagged_unwrap_or_else(result, tag, func_or_value)

Specs

tagged_unwrap_or_else(
  result_input(),
  atom(),
  OkThen.Result.Private.func_or_value(any())
) :: any()

Returns the wrapped value if result is tagged with the specified tag atom. Otherwise, passes the tag and wrapped value into the provided function and returns the result. If the function has arity 1, then only the wrapped value is passed in. An arity-0 function is also accepted. If func_or_value is not a function, then it is used directly as the new value.

See also tagged_or_else/3.

Examples

iex> {:ok, "hello"} |> Result.tagged_unwrap_or_else(:ok, "default")
"hello"

iex> :some |> Result.tagged_unwrap_or_else(:some, "default")
{}

iex> :error |> Result.tagged_unwrap_or_else(:ok, "default")
"default"

iex> {:error, "hello"} |> Result.tagged_unwrap_or_else(:ok, "default")
"default"

iex> {:error, "hello"}
...> |> Result.tagged_unwrap_or_else(:ok, fn
...>   :error, "hello" -> "default"
...> end)
"default"

iex> {:error, "hello"}
...> |> Result.tagged_unwrap_or_else(:ok, fn
...>   "hello" -> "default"
...> end)
"default"

iex> {:error, "hello"} |> Result.tagged_unwrap_or_else(:ok, fn -> "default" end)
"default"

iex> :none |> Result.tagged_unwrap_or_else(:ok, "default")
"default"

iex> "hello" |> Result.tagged_unwrap_or_else(:ok, "default")
"default"

iex> "hello" |> Result.tagged_unwrap_or_else(:untagged, "default")
"hello"

Link to this section Functions

Specs

from(v) :: maybe(v) when v: any()

Converts value into a maybe(v) result: {:ok, value} | :none

If value is nil, then the result will be :none. See also from!/1.

Otherwise, the result will be a two-element tuple, where the first element is :ok, and the second element is value.

Examples

iex> Result.from("hello")
{:ok, "hello"}

iex> Result.from({1, 2})
{:ok, {1, 2}}

iex> Result.from({})
:ok

iex> Result.from(nil)
:none

Specs

from!(t) :: ok(t) when t: any()

Same as from/1, except raises ArgumentError if value is nil.

Examples

iex> Result.from!("hello")
{:ok, "hello"}

iex> Result.from!({1, 2})
{:ok, {1, 2}}

iex> Result.from!(nil)
** (ArgumentError) Value is nil.
Link to this function

from_as(value, tag)

Specs

from_as(v, atom()) :: maybe_is(v) when v: any()

Converts value into a maybe_is(tag) result: {atom(), any()} | :none

If value is nil, then the result will be :none. See also from_as!/2.

Otherwise, the result will be a two-element tuple, where the first element is the provided tag, and the second element is value.

Examples

iex> "hello" |> Result.from_as(:ok)
{:ok, "hello"}

iex> Result.from_as({1, 2}, :something)
{:something, {1, 2}}

iex> Result.from_as({}, :any_atom)
:any_atom

iex> Result.from_as(nil, :ok)
:none
Link to this function

from_as!(value, tag)

Specs

from_as!(v, atom()) :: maybe_is(v) when v: any()

Same as from_as/2, except raises ArgumentError if value is nil.

Examples

iex> "hello" |> Result.from_as!(:ok)
{:ok, "hello"}

iex> nil |> Result.from_as!(:ok)
** (ArgumentError) Value is nil.
Link to this function

from_error(value)

Specs

from_error(e) :: maybe_error(e) when e: any()

Converts value into a maybe_error(e) result: {:error, value} | :none

If value is nil, then the result will be :none. See also from_error!/1.

Otherwise, the result will be a two-element tuple, where the first element is :error, and the second element is value.

Examples

iex> Result.from_error("hello")
{:error, "hello"}

iex> Result.from_error({1, 2})
{:error, {1, 2}}

iex> Result.from_error({})
:error

iex> Result.from_error(nil)
:none
Link to this function

from_error!(value)

Specs

from_error!(e) :: error(e) when e: any()

Same as from_error/1, except raises ArgumentError if value is nil.

Examples

iex> Result.from_error!("hello")
{:error, "hello"}

iex> Result.from_error!({1, 2})
{:error, {1, 2}}

iex> Result.from_error!(nil)
** (ArgumentError) Value is nil.
Link to this function

normalize(result, default_tag \\ :untagged)

Specs

normalize(result_input(), atom()) :: tagged()

Converts result from a variety of accepted result-like terms into an atom or a two-element tagged tuple.

If result is not a tagged tuple, it is wrapped as a new result

Examples

iex> Result.normalize(:ok)
:ok

iex> Result.normalize({:ok, "hello"})
{:ok, "hello"}

iex> Result.normalize({:ok, 1, 2})
{:ok, {1, 2}}

iex> Result.normalize(:error)
:error

iex> Result.normalize({:error, "hello"})
{:error, "hello"}

iex> Result.normalize({:error, 1, 2})
{:error, {1, 2}}

iex> Result.normalize(:none)
:none

iex> Result.normalize({:strange, ["hello", 1, 2]})
{:strange, ["hello", 1, 2]}

iex> Result.normalize("hello")
{:untagged, "hello"}

iex> Result.normalize({1, 2})
{:untagged, {1, 2}}

iex> Result.normalize({})
:untagged

iex> Result.normalize({1, 2}, :error)
{:error, {1, 2}}

iex> Result.normalize(nil)
:none

iex> Result.normalize(nil, :error)
:none
Link to this function

normalize!(result)

Specs

normalize!(result_input()) :: tagged()

Same as normalize/1, except raises ArgumentError if value is untagged.

Examples

iex> Result.normalize({:ok, "hello"})
{:ok, "hello"}

iex> Result.normalize!("hello")
** (ArgumentError) Result is untagged: "hello"