View Source Funx.Monad.Either (funx v0.1.7)
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 theRightbranch.left/1: Wraps a value in theLeftbranch.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 aLeftusing a function, leavingRightvalues unchanged.flip/1: SwapsLeftandRight, turning errors into successes and vice versa.filter_or_else/3: Applies a predicate to theRightvalue; if false, returns a fallbackLeft.
List Operations
concat/1: Removes allLeftvalues and unwraps theRightvalues from a list.concat_map/2: Applies a function and collects onlyRightresults.sequence/1: Converts a list ofEithervalues into a singleEitherof 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 fromLeftvalues.traverse_a/2: Liketraverse/2, but accumulates allLeftvalues instead of short-circuiting.wither_a/2: Liketraverse_a/2, but filters outNothingresults and collects onlyJustvalues.
Validation
validate/2: Applies multiple validators to a single input, collecting all errors.
Lifting
lift_predicate/3: Turns a predicate into anEither, returningRightontrueandLeftonfalse.lift_maybe/2: Converts aMaybeto anEitherusing a fallback value.lift_eq/1: Lifts an equality function into theEithercontext.lift_ord/1: Lifts an ordering function into theEithercontext.
## Transformation
map_left/2– Transforms the error inside aLeft, leavingRightvalues untouched.
Elixir Interoperability
from_result/1: Converts{:ok, val}or{:error, err}into anEither.to_result/1: Converts anEitherinto a result tuple.from_try/1: Runs a function and returnsRighton success orLefton 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 betweenEithervalues.Funx.Foldable: Implementsfold_l/3andfold_r/3for reducing over contained values.Funx.Monad: Providesmap/2,ap/2, andbind/2for monadic composition.Funx.Ord: Defines ordering behavior for comparingLeftandRightvalues.
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:
RightvsRight: Uses the custom equality function.LeftvsLeft: Uses the custom equality function.LeftvsRightor 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\""]}