View Source Funx.Monad.Either (funx v0.1.0)
The Funx.Monad.Either
module provides an implementation of the Either
monad, a functional abstraction used to model computations that may fail.
An Either
represents one of two possibilities:
Right(value)
: a successful resultLeft(error)
: a failure or error
This pattern is commonly used in place of exceptions to handle errors explicitly and safely in functional pipelines.
Constructors
right/1
: Wraps a value in theRight
branch.left/1
: Wraps a value in theLeft
branch.pure/1
: Alias forright/1
.
Refinement
Fallback and Extraction
get_or_else/2
: Returns the value from aRight
, or a default ifLeft
.or_else/2
: Returns the originalRight
, or invokes a fallback function ifLeft
.map_left/2
: Transforms aLeft
using a function, leavingRight
values unchanged.flip/1
: SwapsLeft
andRight
, turning errors into successes and vice versa.filter_or_else/3
: Applies a predicate to theRight
value; if false, returns a fallbackLeft
.
List Operations
concat/1
: Removes allLeft
values and unwraps theRight
values from a list.concat_map/2
: Applies a function and collects onlyRight
results.sequence/1
: Converts a list ofEither
values into a singleEither
of list.traverse/2
: Applies a function to each element in a list and sequences the results.sequence_a/1
: Likesequence/1
, but accumulates all errors fromLeft
values.traverse_a/2
: Liketraverse/2
, but accumulates allLeft
values instead of short-circuiting.wither_a/2
: Liketraverse_a/2
, but filters outNothing
results and collects onlyJust
values.
Validation
validate/2
: Applies multiple validators to a single input, collecting all errors.
Lifting
lift_predicate/3
: Turns a predicate into anEither
, returningRight
ontrue
andLeft
onfalse
.lift_maybe/2
: Converts aMaybe
to anEither
using a fallback value.lift_eq/1
: Lifts an equality function into theEither
context.lift_ord/1
: Lifts an ordering function into theEither
context.
## Transformation
map_left/2
– Transforms the error inside aLeft
, leavingRight
values untouched.
Elixir Interoperability
from_result/1
: Converts{:ok, val}
or{:error, err}
into anEither
.to_result/1
: Converts anEither
into a result tuple.from_try/1
: Runs a function and returnsRight
on success orLeft
on exception.to_try!/1
: Unwraps aRight
, or raises an error from aLeft
.
Protocols
The Left
and Right
structs implement the following protocols, making the Either
abstraction composable and extensible:
Funx.Eq
: Enables equality comparisons betweenEither
values.Funx.Foldable
: Implementsfold_l/3
andfold_r/3
for reducing over contained values.Funx.Monad
: Providesmap/2
,ap/2
, andbind/2
for monadic composition.Funx.Ord
: Defines ordering behavior for comparingLeft
andRight
values.
Although these implementations are defined on each constructor (Left
and Right
), the behavior is consistent across the Either
abstraction.
This module helps you model failure explicitly, compose error-aware logic, and integrate cleanly with Elixir's functional idioms.
Summary
Functions
Removes Left
values from a list of Either
and returns a list of unwrapped Right
values.
Applies the given function to each element in the list and collects the Right
results, discarding any Left
.
Filters the value inside a Right
using the given predicate
. If the predicate returns false
,
a Left
is returned using the left_func
.
Swaps the Left
and Right
branches of the Either
.
Converts a result ({:ok, _}
or {:error, _}
) to an Either
.
Wraps a value in an Either
, catching any exceptions. If an exception occurs, a Left
is returned with the exception.
Retrieves the value from a Right
, returning the default
value if Left
.
Wraps a value in the Left
monad.
Returns true
if the Either
is a Left
value.
Lifts an equality function to compare Either
values
Converts a Maybe
value to an Either
. If the Maybe
is Nothing
, a Left
is returned using on_none
.
Creates a custom ordering function for Either
values using the provided custom_ord
.
Lifts a value into an Either
based on the result of a predicate.
Transforms the Left
value using the given function if the Either
is a Left
.
If the value is Right
, it is returned unchanged.
Returns the current Right
value or invokes the fallback_fun
if Left
.
Alias for right/1
.
Wraps a value in the Right
monad.
Returns true
if the Either
is a Right
value.
Sequences a list of Either
values into an Either
of a list.
Sequences a list of Either
values, collecting all errors from Left
values, rather than short-circuiting.
Converts an Either
to a result ({:ok, value}
or {:error, reason}
).
Converts an Either
to its inner value, raising an exception if it is Left
.
Traverses a list, applying the given function to each element and collecting the results in a single Right
, or short-circuiting with the first Left
.
Traverses a list, applying the given function to each element and collecting the results in a single Right
.
Validates a value using a list of validator functions. Each validator returns an Either.Right
if
the check passes, or an Either.Left
with an error message if it fails. If any validation fails,
all errors are aggregated and returned in a single Left
.
Traverses a list, applying the given function to each element, and collects the successful Just
results into a single Right
.
Types
@type t(left, right) :: Funx.Monad.Either.Left.t(left) | Funx.Monad.Either.Right.t(right)
Functions
Removes Left
values from a list of Either
and returns a list of unwrapped Right
values.
Useful for discarding failed computations while keeping successful results.
Examples
iex> Funx.Monad.Either.concat([Funx.Monad.Either.right(1), Funx.Monad.Either.left(:error), Funx.Monad.Either.right(2)])
[1, 2]
iex> Funx.Monad.Either.concat([Funx.Monad.Either.left(:a), Funx.Monad.Either.left(:b)])
[]
iex> Funx.Monad.Either.concat([Funx.Monad.Either.right("a"), Funx.Monad.Either.right("b"), Funx.Monad.Either.right("c")])
["a", "b", "c"]
@spec concat_map([input], (input -> t(error, output))) :: [output] when input: any(), output: any(), error: any()
Applies the given function to each element in the list and collects the Right
results, discarding any Left
.
This is useful when mapping a function that may fail and you only want the successful results.
Examples
iex> Funx.Monad.Either.concat_map([1, 2, 3], fn x -> if rem(x, 2) == 1, do: Funx.Monad.Either.right(x), else: Funx.Monad.Either.left(:even) end)
[1, 3]
iex> Funx.Monad.Either.concat_map([2, 4], fn x -> if x > 3, do: Funx.Monad.Either.right(x), else: Funx.Monad.Either.left(:too_small) end)
[4]
iex> Funx.Monad.Either.concat_map([], fn _ -> Funx.Monad.Either.left(:none) end)
[]
Filters the value inside a Right
using the given predicate
. If the predicate returns false
,
a Left
is returned using the left_func
.
Examples
iex> Funx.Monad.Either.filter_or_else(Funx.Monad.Either.right(5), fn x -> x > 3 end, fn -> "error" end)
%Funx.Monad.Either.Right{right: 5}
iex> Funx.Monad.Either.filter_or_else(Funx.Monad.Either.right(2), fn x -> x > 3 end, fn -> "error" end)
%Funx.Monad.Either.Left{left: "error"}
Swaps the Left
and Right
branches of the Either
.
Turns a Left
into a Right
and vice versa, preserving the contained term.
Examples
iex> Funx.Monad.Either.flip(Funx.Monad.Either.left(:error))
%Funx.Monad.Either.Right{right: :error}
iex> Funx.Monad.Either.flip(Funx.Monad.Either.right(42))
%Funx.Monad.Either.Left{left: 42}
Converts a result ({:ok, _}
or {:error, _}
) to an Either
.
Examples
iex> Funx.Monad.Either.from_result({:ok, 5})
%Funx.Monad.Either.Right{right: 5}
iex> Funx.Monad.Either.from_result({:error, "error"})
%Funx.Monad.Either.Left{left: "error"}
@spec from_try((-> right)) :: t(Exception.t(), right) when right: term()
Wraps a value in an Either
, catching any exceptions. If an exception occurs, a Left
is returned with the exception.
Examples
iex> Funx.Monad.Either.from_try(fn -> 5 end)
%Funx.Monad.Either.Right{right: 5}
iex> Funx.Monad.Either.from_try(fn -> raise "error" end)
%Funx.Monad.Either.Left{left: %RuntimeError{message: "error"}}
Retrieves the value from a Right
, returning the default
value if Left
.
Examples
iex> Funx.Monad.Either.get_or_else(Funx.Monad.Either.right(5), 0)
5
iex> Funx.Monad.Either.get_or_else(Funx.Monad.Either.left("error"), 0)
0
@spec left(any()) :: Funx.Monad.Either.Left.t(any())
Wraps a value in the Left
monad.
Examples
iex> Funx.Monad.Either.left("error")
%Funx.Monad.Either.Left{left: "error"}
Returns true
if the Either
is a Left
value.
Examples
iex> Funx.Monad.Either.left?(Funx.Monad.Either.left("error"))
true
iex> Funx.Monad.Either.left?(Funx.Monad.Either.right(5))
false
@spec lift_eq(Funx.Eq.Utils.eq_t()) :: Funx.Eq.Utils.eq_map()
Lifts an equality function to compare Either
values:
Right
vsRight
: Uses the custom equality function.Left
vsLeft
: Uses the custom equality function.Left
vsRight
or vice versa: Alwaysfalse
.
Examples
iex> eq = Funx.Monad.Either.lift_eq(%{
...> eq?: fn x, y -> x == y end,
...> not_eq?: fn x, y -> x != y end
...> })
iex> eq.eq?.(Funx.Monad.Either.right(5), Funx.Monad.Either.right(5))
true
iex> eq.eq?.(Funx.Monad.Either.right(5), Funx.Monad.Either.right(10))
false
iex> eq.eq?.(Funx.Monad.Either.left(:a), Funx.Monad.Either.left(:a))
true
iex> eq.eq?.(Funx.Monad.Either.left(:a), Funx.Monad.Either.left(:b))
false
iex> eq.eq?.(Funx.Monad.Either.right(5), Funx.Monad.Either.left(:a))
false
Converts a Maybe
value to an Either
. If the Maybe
is Nothing
, a Left
is returned using on_none
.
Examples
iex> Funx.Monad.Either.lift_maybe(Funx.Monad.Maybe.just(5), fn -> "error" end)
%Funx.Monad.Either.Right{right: 5}
iex> Funx.Monad.Either.lift_maybe(Funx.Monad.Maybe.nothing(), fn -> "error" end)
%Funx.Monad.Either.Left{left: "error"}
@spec lift_ord(Funx.Ord.Utils.ord_t()) :: Funx.Ord.Utils.ord_map()
Creates a custom ordering function for Either
values using the provided custom_ord
.
The custom_ord
must be a map with :lt?
, :le?
, :gt?
, and :ge?
functions. These are used to compare the internal left
or right
values.
Examples
iex> ord = Funx.Monad.Either.lift_ord(%{
...> lt?: fn x, y -> x < y end,
...> le?: fn x, y -> x <= y end,
...> gt?: fn x, y -> x > y end,
...> ge?: fn x, y -> x >= y end
...> })
iex> ord.lt?.(Funx.Monad.Either.right(3), Funx.Monad.Either.right(5))
true
iex> ord.lt?.(Funx.Monad.Either.left(3), Funx.Monad.Either.right(5))
true
iex> ord.lt?.(Funx.Monad.Either.right(3), Funx.Monad.Either.left(5))
false
iex> ord.lt?.(Funx.Monad.Either.left(3), Funx.Monad.Either.left(5))
true
@spec lift_predicate(value, (value -> boolean()), (value -> error)) :: t(error, value) when value: term(), error: term()
Lifts a value into an Either
based on the result of a predicate.
Returns Right(value)
if the predicate returns true
, or Left(on_false.(value))
if it returns false
.
This allows you to wrap a conditional check in a functional context with a custom error message.
Examples
iex> Funx.Monad.Either.lift_predicate(5, fn x -> x > 3 end, fn x -> "#{x} is too small" end)
%Funx.Monad.Either.Right{right: 5}
iex> Funx.Monad.Either.lift_predicate(2, fn x -> x > 3 end, fn x -> "#{x} is too small" end)
%Funx.Monad.Either.Left{left: "2 is too small"}
@spec map_left(t(error, value), (error -> new_error)) :: t(new_error, value) when error: term(), new_error: term(), value: term()
Transforms the Left
value using the given function if the Either
is a Left
.
If the value is Right
, it is returned unchanged.
Examples
iex> Funx.Monad.Either.map_left(Funx.Monad.Either.left("error"), fn e -> "wrapped: " <> e end)
%Funx.Monad.Either.Left{left: "wrapped: error"}
iex> Funx.Monad.Either.map_left(Funx.Monad.Either.right(42), fn _ -> "ignored" end)
%Funx.Monad.Either.Right{right: 42}
@spec or_else(t(error, value), (-> t(error, value))) :: t(error, value) when error: term(), value: term()
Returns the current Right
value or invokes the fallback_fun
if Left
.
Useful for recovering from a failure by providing an alternate computation.
Examples
iex> Funx.Monad.Either.or_else(Funx.Monad.Either.left("error"), fn -> Funx.Monad.Either.right(42) end)
%Funx.Monad.Either.Right{right: 42}
iex> Funx.Monad.Either.or_else(Funx.Monad.Either.right(10), fn -> Funx.Monad.Either.right(42) end)
%Funx.Monad.Either.Right{right: 10}
@spec pure(any()) :: Funx.Monad.Either.Right.t(any())
Alias for right/1
.
Examples
iex> Funx.Monad.Either.pure(2)
%Funx.Monad.Either.Right{right: 2}
@spec right(any()) :: Funx.Monad.Either.Right.t(any())
Wraps a value in the Right
monad.
Examples
iex> Funx.Monad.Either.right(5)
%Funx.Monad.Either.Right{right: 5}
Returns true
if the Either
is a Right
value.
Examples
iex> Funx.Monad.Either.right?(Funx.Monad.Either.right(5))
true
iex> Funx.Monad.Either.right?(Funx.Monad.Either.left("error"))
false
Sequences a list of Either
values into an Either
of a list.
Examples
iex> Funx.Monad.Either.sequence([Funx.Monad.Either.right(1), Funx.Monad.Either.right(2)])
%Funx.Monad.Either.Right{right: [1, 2]}
iex> Funx.Monad.Either.sequence([Funx.Monad.Either.right(1), Funx.Monad.Either.left("error")])
%Funx.Monad.Either.Left{left: "error"}
Sequences a list of Either
values, collecting all errors from Left
values, rather than short-circuiting.
Examples
iex> Funx.Monad.Either.sequence_a([Funx.Monad.Either.right(1), Funx.Monad.Either.left("error"), Funx.Monad.Either.left("another error")])
%Funx.Monad.Either.Left{left: ["error", "another error"]}
Converts an Either
to a result ({:ok, value}
or {:error, reason}
).
Examples
iex> Funx.Monad.Either.to_result(Funx.Monad.Either.right(5))
{:ok, 5}
iex> Funx.Monad.Either.to_result(Funx.Monad.Either.left("error"))
{:error, "error"}
Converts an Either
to its inner value, raising an exception if it is Left
.
If the Left
holds an exception struct, it is raised directly. If it holds a string or list of errors, they are converted into a RuntimeError
. Unexpected types are inspected and raised as a RuntimeError
.
Examples
iex> Funx.Monad.Either.to_try!(Funx.Monad.Either.right(5))
5
iex> Funx.Monad.Either.to_try!(Funx.Monad.Either.left("error"))
** (RuntimeError) error
iex> Funx.Monad.Either.to_try!(Funx.Monad.Either.left(["error 1", "error 2"]))
** (RuntimeError) error 1, error 2
iex> Funx.Monad.Either.to_try!(Funx.Monad.Either.left(%ArgumentError{message: "bad argument"}))
** (ArgumentError) bad argument
Traverses a list, applying the given function to each element and collecting the results in a single Right
, or short-circuiting with the first Left
.
This is useful for validating or transforming a list of values where each step may fail.
Examples
iex> Funx.Monad.Either.traverse([1, 2, 3], &Funx.Monad.Either.right/1)
%Funx.Monad.Either.Right{right: [1, 2, 3]}
iex> Funx.Monad.Either.traverse([1, -2, 3], fn x -> if x > 0, do: Funx.Monad.Either.right(x), else: Funx.Monad.Either.left("error") end)
%Funx.Monad.Either.Left{left: "error"}
Traverses a list, applying the given function to each element and collecting the results in a single Right
.
Unlike traverse/2
, this version accumulates all Left
values rather than stopping at the first failure.
It is useful for validations where you want to gather all errors at once.
Examples
iex> validate = fn x -> Funx.Monad.Either.lift_predicate(x, &(&1 > 0), fn v -> "must be positive: #{v}" end) end
iex> Funx.Monad.Either.traverse_a([1, 2, 3], validate)
%Funx.Monad.Either.Right{right: [1, 2, 3]}
iex> Funx.Monad.Either.traverse_a([1, -2, -3], validate)
%Funx.Monad.Either.Left{left: ["must be positive: -2", "must be positive: -3"]}
@spec validate(value, [(value -> t(error, any()))]) :: t([error], value) when error: term(), value: term()
Validates a value using a list of validator functions. Each validator returns an Either.Right
if
the check passes, or an Either.Left
with an error message if it fails. If any validation fails,
all errors are aggregated and returned in a single Left
.
Flat list aggregation
When using the default aggregation strategy, errors are collected in a plain list:
validate_positive = fn x ->
Funx.Monad.Either.lift_predicate(x, &(&1 > 0), fn v -> "Value must be positive: " <> to_string(v) end)
end
validate_even = fn x ->
Funx.Monad.Either.lift_predicate(x, &(rem(&1, 2) == 0), fn v -> "Value must be even: " <> to_string(v) end)
end
Funx.Monad.Either.validate(4, [validate_positive, validate_even])
#=> %Funx.Monad.Either.Right{right: 4}
Funx.Monad.Either.validate(3, [validate_positive, validate_even])
#=> %Funx.Monad.Either.Left{left: ["Value must be even: 3"]}
Funx.Monad.Either.validate(-3, [validate_positive, validate_even])
#=> %Funx.Monad.Either.Left{left: ["Value must be positive: -3", "Value must be even: -3"]}
Structured aggregation with ValidationError
You can also use a custom struct to hold errors. This example uses ValidationError
and a corresponding
Funx.Semigroup
implementation to accumulate errors into a single structure:
alias Funx.Errors.ValidationError
validate_positive = fn x ->
Funx.Monad.Either.lift_predicate(x, &(&1 > 0), fn v -> "Value must be positive: " <> to_string(v) end)
|> Funx.Monad.Either.map_left(&ValidationError.new/1)
end
validate_even = fn x ->
Funx.Monad.Either.lift_predicate(x, &(rem(&1, 2) == 0), fn v -> "Value must be even: " <> to_string(v) end)
|> Funx.Monad.Either.map_left(&ValidationError.new/1)
end
Funx.Monad.Either.validate(-3, [validate_positive, validate_even])
#=> %Funx.Monad.Either.Left{
# left: %ValidationError{
# errors: ["Value must be positive: -3", "Value must be even: -3"]
# }
# }
@spec wither_a([a], (a -> t([e], Funx.Monad.Maybe.t(b)))) :: t([e], [b]) when a: term(), b: term(), e: term()
Traverses a list, applying the given function to each element, and collects the successful Just
results into a single Right
.
The given function must return an Either
of Maybe
. Right(Just x)
values are kept; Right(Nothing)
values are filtered out.
If any application returns Left
, all Left
values are accumulated.
This is useful for effectful filtering, where you want to validate or transform elements and conditionally keep them, while still reporting all errors.
Examples
iex> filter_positive = fn x ->
...> Funx.Monad.Either.lift_predicate(x, &is_integer/1, fn v -> "not an integer: #{inspect(v)}" end)
...> |> Funx.Monad.map(fn x -> if x > 0, do: Funx.Monad.Maybe.just(x), else: Funx.Monad.Maybe.nothing() end)
...> end
iex> Funx.Monad.Either.wither_a([1, -2, 3], filter_positive)
%Funx.Monad.Either.Right{right: [1, 3]}
iex> Funx.Monad.Either.wither_a(["oops", -2], filter_positive)
%Funx.Monad.Either.Left{left: ["not an integer: \"oops\""]}