View Source Funx.Monad.Effect (funx v0.1.7)
The Funx.Monad.Effect module defines the Effect monad, which represents asynchronous computations
that may succeed (Right) or fail (Left). Execution is deferred until explicitly run, making
Effect useful for structuring lazy, asynchronous workflows.
This module integrates tracing and telemetry, making it suitable for observability in concurrent
Elixir systems. All effects carry a Effect.Context, which links operations and records spans
when run/2 is called.
Constructors
right/1– Wraps a value in a successfulRighteffect.left/1– Wraps a value in a failingLefteffect.pure/1– Alias forright/1.
Execution
run/2– Executes the deferred effect and returns anEitherresult (RightorLeft).
You may pass :task_supervisor in the opts to run the effect under a specific Task.Supervisor. This supervises the top-level task, any internal tasks spawned within the effect function are not supervised.
Sequencing
sequence/1– Runs a list of effects, stopping at the firstLeft.traverse/2– Applies a function returning anEffectto each element of a list, sequencing results.sequence_a/2– Runs a list of effects, collecting allLefterrors instead of short-circuiting.traverse_a/3– Liketraverse/2, but accumulates errors across the list.
Validation
validate/2– Validates a value using one or more effectful validators.
Error Handling
map_left/2– Transforms aLeftusing a function, leavingRightvalues unchanged.flip_either/1– Inverts the success and failure branches of anEffect.
Lifting
lift_func/2– Lifts a thunk that returns any value into anEffect, wrapping it inRight. If the thunk raises, the error is captured as aLeft(EffectError).lift_either/2– Lifts a thunk that returns anEitherinto anEffect. Evaluation is deferred until the effect is run. Errors are also captured and wrapped inLeft(EffectError).lift_maybe/3– Lifts aMaybeinto anEffect, using a fallback error if the value isNothing.lift_predicate/3– Lifts a predicate check into anEffect. ReturnsRight(value)if the predicate passes; otherwise returnsLeft(fallback).
Reader Operations
ask/0– Returns the environment passed torun/2as aRight.asks/1– Applies a function to the environment passed torun/2, wrapping the result in aRight.fail/0– Returns the environment passed torun/2as aLeft.fails/1– Applies a function to the environment passed torun/2, wrapping the result in aLeft.
Elixir Interop
from_result/2– Converts a{:ok, _}or{:error, _}tuple into anEffect.to_result/1– Converts anEffectto{:ok, _}or{:error, _}.from_try/2– Wraps a function that may raise, returning Right on success, or Left if an exception is raised.to_try!/1– Extracts the value from aRight, or raises an exception ifLeft.
Protocols
The Left and Right structs implement the following protocols:
- Funx.Monad – Provides map/2, ap/2, and bind/2 for compositional workflows.
Although protocol implementations are defined on Left and Right individually, the behavior is unified under the Effect abstraction.
This module enables structured concurrency, error handling, and observability in asynchronous workflows.
Telemetry
The run/2 function emits telemetry using :telemetry.span/3.
Events
[:funx, :effect, :run, :start][:funx, :effect, :run, :stop]
Measurements
:monotonic_time– included in both:startand:stopevents.:system_time– included only in the:startevent.:duration– included only in the:stopevent.
Metadata
:timeout– the timeout in milliseconds passed torun/2.:result– a summarized version of the result usingFunx.Summarizable.:effect_type–:rightor:left, depending on the effect being run.:status–:okif the result is aRight, or:errorif it's aLeft.:trace_id– optional value used to correlate traces across boundaries.:span_name– optional name for the span (defaults to"funx.effect.run").:telemetry_span_context– reference to correlate:startand:stopevents.
Example
:telemetry.attach(
"effect-run-handler",
[:funx, :effect, :run, :stop],
fn event, measurements, metadata, _config ->
IO.inspect({event, measurements, metadata}, label: "Effect telemetry")
end,
nil
)
Summary
Types
Represents a deferred computation in the Effect monad that may either succeed (Right) or fail (Left).
Functions
Returns a Funx.Monad.Effect.Right that yields the environment passed to Funx.Monad.Effect.run/2.
Returns a Funx.Monad.Effect.Right that applies the given function to the environment passed to Funx.Monad.Effect.run/2.
Returns a Funx.Monad.Effect.Left that fails with the entire environment passed to Funx.Monad.Effect.run/2.
Returns a Funx.Monad.Effect.Left that applies the given function to the environment passed to Funx.Monad.Effect.run/2.
Inverts the success and failure branches of an Effect.
Converts an Elixir {:ok, value} or {:error, reason} tuple into an Effect.
Lifts a potentially exception-raising function into a Kleisli function for the Effect monad.
Wraps a value in the Left variant of the Effect monad, representing a failed asynchronous computation.
Lifts a thunk that returns an Either into the Effect monad.
Lifts a thunk into the Effect monad, wrapping its result in a Right.
Converts a Maybe value into the Effect monad.
If the Maybe is Just, the value is wrapped in Right.
If it is Nothing, the result of on_none is wrapped in Left.
Lifts a value into the Effect monad based on a predicate.
If the predicate returns true, the value is wrapped in Right.
Otherwise, the result of calling on_false with the value is wrapped in Left.
Transforms the Left branch of an Effect.
Alias for right/2.
Wraps a value in the Right variant of the Effect monad, representing a successful asynchronous computation.
Runs the Effect and returns the result, awaiting the task if necessary.
Sequences a list of Effect computations, running each in order.
Sequences a list of Effect computations, collecting all Right results
or accumulating all Left errors if present.
Converts an Effect into an Elixir {:ok, _} or {:error, _} tuple by running the effect.
Executes an Effect and returns the result if it is a Right. If the result is a Left,
this function raises the contained error.
Traverses a list with a function that returns Effect computations,
running each in sequence and collecting the Right results.
Traverses a list with a function that returns Effect values, combining results
into a single Effect. Unlike traverse/2, this version accumulates all errors
rather than stopping at the first Left.
Validates a value using one or more validator functions, each returning an Effect.
Types
@type t(left, right) :: Funx.Monad.Effect.Left.t(left) | Funx.Monad.Effect.Right.t(right)
Represents a deferred computation in the Effect monad that may either succeed (Right) or fail (Left).
This type unifies Effect.Right.t/1 and Effect.Left.t/1 under a common interface, allowing code to
operate over asynchronous effects regardless of success or failure outcome.
Each variant carries a context for telemetry and a deferred effect function that takes an environment.
Functions
@spec ask() :: Funx.Monad.Effect.Right.t()
Returns a Funx.Monad.Effect.Right that yields the environment passed to Funx.Monad.Effect.run/2.
This is the Reader-style ask, used to access the full environment inside an effectful computation.
Example
iex> Funx.Monad.Effect.ask()
...> |> Funx.Monad.map(& &1[:region])
...> |> Funx.Monad.Effect.run(%{region: "us-west"})
%Funx.Monad.Either.Right{right: "us-west"}
@spec asks((term() -> term())) :: Funx.Monad.Effect.Right.t()
Returns a Funx.Monad.Effect.Right that applies the given function to the environment passed to Funx.Monad.Effect.run/2.
This allows extracting a value from the environment and using it in an effectful computation, following the Reader pattern.
Example
iex> Funx.Monad.Effect.asks(fn env -> env[:user] end)
...> |> Funx.Monad.bind(fn user -> Funx.Monad.Effect.right(user) end)
...> |> Funx.Monad.Effect.run(%{user: "alice"})
%Funx.Monad.Either.Right{right: "alice"}
@spec await(Task.t(), timeout()) :: Funx.Monad.Either.t(any(), any())
@spec fail() :: Funx.Monad.Effect.Left.t()
Returns a Funx.Monad.Effect.Left that fails with the entire environment passed to Funx.Monad.Effect.run/2.
This is the Reader-style equivalent of ask/0, but marks the environment as a failure.
Useful when the presence of certain runtime data should short-circuit execution.
Example
iex> Funx.Monad.Effect.fail()
...> |> Funx.Monad.Effect.run(%{error: :invalid_token})
%Funx.Monad.Either.Left{left: %{error: :invalid_token}}
@spec fails((term() -> term())) :: Funx.Monad.Effect.Left.t()
Returns a Funx.Monad.Effect.Left that applies the given function to the environment passed to Funx.Monad.Effect.run/2.
This is the failure-side equivalent of asks/1, used to produce an error effect based on runtime context.
Example
iex> Funx.Monad.Effect.fails(fn env -> {:missing_key, env} end)
...> |> Funx.Monad.Effect.run(%{input: nil})
%Funx.Monad.Either.Left{left: {:missing_key, %{input: nil}}}
Inverts the success and failure branches of an Effect.
For a Right, this reverses the result: a successful value becomes a failure, and
a failure becomes a success. For a Left, only failure is expected; if the Left
produces a success, it is ignored.
This is useful when you want to reverse the semantics of a computation—treating an expected error as success, or vice versa.
Examples
iex> effect = Funx.Monad.Effect.pure(42)
iex> flipped = Funx.Monad.Effect.flip_either(effect)
iex> Funx.Monad.Effect.run(flipped)
%Funx.Monad.Either.Left{left: 42}
iex> effect = Funx.Monad.Effect.left("fail")
iex> flipped = Funx.Monad.Effect.flip_either(effect)
iex> Funx.Monad.Effect.run(flipped)
%Funx.Monad.Either.Right{right: "fail"}
@spec from_result( {:ok, right} | {:error, left}, Funx.Monad.Effect.Context.opts_or_context() ) :: t(left, right) when left: term(), right: term()
Converts an Elixir {:ok, value} or {:error, reason} tuple into an Effect.
Accepts an optional context context which includes telemetry tracking.
Examples
iex> result = Funx.Monad.Effect.from_result({:ok, 42})
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: 42}
iex> result = Funx.Monad.Effect.from_result({:error, "error"})
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{left: "error"}
@spec from_try((input -> right), Funx.Monad.Effect.Context.opts_or_context()) :: (input -> t( Exception.t(), right )) when input: term(), right: term()
Lifts a potentially exception-raising function into a Kleisli function for the Effect monad.
This returns a function of type (input -> Effect) that applies the given function to a value.
If the function raises, the error is captured and returned in a Left. You can optionally
provide a context (or opts) for tracing and telemetry.
Examples
iex> safe_div = Funx.Monad.Effect.from_try(fn x -> 10 / x end)
iex> effect = Funx.Monad.Effect.pure(2) |> Funx.Monad.bind(safe_div)
iex> Funx.Monad.Effect.run(effect)
%Funx.Monad.Either.Right{right: 5.0}
iex> bad_div = Funx.Monad.Effect.pure(0) |> Funx.Monad.bind(safe_div)
iex> Funx.Monad.Effect.run(bad_div)
%Funx.Monad.Either.Left{left: %ArithmeticError{}}
@spec left(left, Funx.Monad.Effect.Context.opts_or_context()) :: t(left, term()) when left: term()
Wraps a value in the Left variant of the Effect monad, representing a failed asynchronous computation.
Accepts either a keyword list of context options or a Effect.Context struct.
Examples
iex> result = Funx.Monad.Effect.left("error")
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{left: "error"}
iex> context = Funx.Monad.Effect.Context.new(trace_id: "err-id", span_name: "failure")
iex> result = Funx.Monad.Effect.left("error", context)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{left: "error"}
@spec lift_either( (-> Funx.Monad.Either.t(left, right)), Funx.Monad.Effect.Context.opts_or_context() ) :: t(left, right) when left: term(), right: term()
Lifts a thunk that returns an Either into the Effect monad.
Instead of passing an Either value directly, you provide a zero-arity function (thunk) that returns one.
This defers execution until the effect is run, allowing integration with tracing and composable pipelines.
You may also pass a context or options (opts) to configure telemetry or span metadata.
If the thunk raises an exception, it is caught and returned as a Left containing an EffectError tagged with :lift.
Examples
iex> result = Funx.Monad.Effect.lift_either(fn -> %Funx.Monad.Either.Right{right: 42} end)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: 42}
iex> result = Funx.Monad.Effect.lift_either(fn -> %Funx.Monad.Either.Left{left: "error"} end)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{left: "error"}
@spec lift_func((-> right), Funx.Monad.Effect.Context.opts_or_context()) :: t(left, right) when left: term(), right: term()
Lifts a thunk into the Effect monad, wrapping its result in a Right.
This function defers execution of the given zero-arity function (thunk) until the effect is run.
The result is automatically wrapped as Either.Right.
You may also pass a context or options (opts) to configure telemetry or span metadata.
If the thunk raises an exception, it is caught and returned as a Left containing an EffectError tagged with :lift.
Examples
iex> result = Funx.Monad.Effect.lift_func(fn -> 42 end)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: 42}
iex> result = Funx.Monad.Effect.lift_func(fn -> raise "boom" end)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{
left: %Funx.Errors.EffectError{stage: :lift_func, reason: %RuntimeError{message: "boom"}}
}
@spec lift_maybe( Funx.Monad.Maybe.t(right), (-> left), Funx.Monad.Effect.Context.opts_or_context() ) :: t(left, right) when left: term(), right: term()
Converts a Maybe value into the Effect monad.
If the Maybe is Just, the value is wrapped in Right.
If it is Nothing, the result of on_none is wrapped in Left.
You can optionally provide context metadata via opts.
Examples
iex> maybe = Funx.Monad.Maybe.just(42)
iex> result = Funx.Monad.Effect.lift_maybe(maybe, fn -> "No value" end)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: 42}
iex> maybe = Funx.Monad.Maybe.nothing()
iex> result = Funx.Monad.Effect.lift_maybe(maybe, fn -> "No value" end)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{left: "No value"}
@spec lift_predicate( term(), (term() -> boolean()), (term() -> left), Funx.Monad.Effect.Context.opts_or_context() ) :: t(left, term()) when left: term()
Lifts a value into the Effect monad based on a predicate.
If the predicate returns true, the value is wrapped in Right.
Otherwise, the result of calling on_false with the value is wrapped in Left.
Optional context metadata (e.g. :span_name, :trace_id) can be passed via opts.
Examples
iex> result = Funx.Monad.Effect.lift_predicate(10, &(&1 > 5), fn x -> "#{x} is too small" end)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: 10}
iex> result = Funx.Monad.Effect.lift_predicate(3, &(&1 > 5), fn x -> "#{x} is too small" end)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{left: "3 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 branch of an Effect.
If the Effect resolves to a Left, the provided function is applied to the error.
If the Effect resolves to a Right, the value is returned unchanged.
This function is useful when you want to rewrite or wrap errors without affecting successful computations.
Examples
iex> effect = Funx.Monad.Effect.left("error")
iex> transformed = Funx.Monad.Effect.map_left(effect, fn e -> "wrapped: " <> e end)
iex> Funx.Monad.Effect.run(transformed)
%Funx.Monad.Either.Left{left: "wrapped: error"}
iex> effect = Funx.Monad.Effect.pure(42)
iex> transformed = Funx.Monad.Effect.map_left(effect, fn _ -> "should not be called" end)
iex> Funx.Monad.Effect.run(transformed)
%Funx.Monad.Either.Right{right: 42}
@spec pure(right, Funx.Monad.Effect.Context.opts_or_context()) :: t(term(), right) when right: term()
Alias for right/2.
Wraps a value in the Right variant of the Effect monad, representing a successful asynchronous computation.
Accepts either a keyword list of context options or a Effect.Context struct.
Examples
iex> result = Funx.Monad.Effect.pure(42)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: 42}
iex> context = Funx.Monad.Effect.Context.new(trace_id: "custom-id", span_name: "pure example")
iex> result = Funx.Monad.Effect.pure(42, context)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: 42}
@spec right(right, Funx.Monad.Effect.Context.opts_or_context()) :: t(term(), right) when right: term()
Wraps a value in the Right variant of the Effect monad, representing a successful asynchronous computation.
This is an alias for pure/2. You may optionally provide execution context, either as a keyword list or
a %Funx.Monad.Effect.Context{} struct. The context is attached to the effect and propagated during execution.
Examples
iex> result = Funx.Monad.Effect.right(42)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: 42}
iex> context = Funx.Monad.Effect.Context.new(trace_id: "custom-id", span_name: "from right")
iex> result = Funx.Monad.Effect.right(42, context)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: 42}
@spec run(t(left, right)) :: Funx.Monad.Either.t(left, right) when left: term(), right: term()
Runs the Effect and returns the result, awaiting the task if necessary.
You may provide optional telemetry metadata using opts, such as :span_name
to promote the current context with a new label.
Options
:span_name– (optional) promotes the trace to a new span with the given name.
Examples
iex> result = Funx.Monad.Effect.right(42)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: 42}
iex> result = Funx.Monad.Effect.right(42, span_name: "initial")
iex> Funx.Monad.Effect.run(result, span_name: "promoted")
%Funx.Monad.Either.Right{right: 42}
@spec run(t(left, right), map()) :: Funx.Monad.Either.t(left, right) when left: term(), right: term()
@spec run( t(left, right), keyword() ) :: Funx.Monad.Either.t(left, right) when left: term(), right: term()
@spec sequence([t(left, right)], Funx.Monad.Effect.Context.opts_or_context()) :: t(left, [right]) when left: term(), right: term()
Sequences a list of Effect computations, running each in order.
If all effects resolve to Right, the result is a Right containing a list of values.
If any effect resolves to Left, the sequencing stops early and that Left is returned.
Each effect is executed with its own context context, and telemetry spans are emitted for observability.
Examples
iex> effects = [Funx.Monad.Effect.right(1), Funx.Monad.Effect.right(2)]
iex> result = Funx.Monad.Effect.sequence(effects)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: [1, 2]}
iex> effects = [Funx.Monad.Effect.right(1), Funx.Monad.Effect.left("error")]
iex> result = Funx.Monad.Effect.sequence(effects)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{left: "error"}
@spec sequence_a([t(error, value)], Funx.Monad.Effect.Context.opts_or_context()) :: t([error], [value]) when error: term(), value: term()
Sequences a list of Effect computations, collecting all Right results
or accumulating all Left errors if present.
Unlike sequence/1, which stops at the first Left, this version continues processing
all effects, returning a list of errors if any failures occur.
Each effect emits its own telemetry span, and error contexts are preserved through tracing.
Examples
iex> effects = [
...> Funx.Monad.Effect.right(1),
...> Funx.Monad.Effect.left("Error 1"),
...> Funx.Monad.Effect.left("Error 2")
...> ]
iex> result = Funx.Monad.Effect.sequence_a(effects)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{left: ["Error 1", "Error 2"]}
@spec to_result( t(left, right), keyword() ) :: {:ok, right} | {:error, left} when left: term(), right: term()
Converts an Effect into an Elixir {:ok, _} or {:error, _} tuple by running the effect.
If the effect completes successfully (Right), the result is wrapped in {:ok, value}.
If the effect fails (Left), the error is returned as {:error, reason}.
This function also emits telemetry via run/2 and supports optional context metadata through keyword options.
Options
:span_name– sets a custom span name for tracing and telemetry.
Examples
iex> effect = Funx.Monad.Effect.right(42, span_name: "convert-ok")
iex> Funx.Monad.Effect.to_result(effect, span_name: "to_result")
{:ok, 42}
iex> error = Funx.Monad.Effect.left("fail", span_name: "convert-error")
iex> Funx.Monad.Effect.to_result(error, span_name: "to_result")
{:error, "fail"}Telemetry will include the promoted span name ("to_result -> convert-ok") and context metadata.
Executes an Effect and returns the result if it is a Right. If the result is a Left,
this function raises the contained error.
This is useful when you want to interoperate with code that expects regular exceptions, such as within test assertions or imperative pipelines.
Runs the effect with full telemetry tracing.
Examples
iex> effect = Funx.Monad.Effect.right(42, span_name: "return")
iex> Funx.Monad.Effect.to_try!(effect)
42
iex> error = Funx.Monad.Effect.left(%RuntimeError{message: "failure"}, span_name: "error")
iex> Funx.Monad.Effect.to_try!(error)
** (RuntimeError) failureTelemetry will emit a :stop event with :status set to :ok or :error, depending on the outcome.
Traverses a list with a function that returns Effect computations,
running each in sequence and collecting the Right results.
If all effects resolve to Right, returns a single Effect with a list of results.
If any effect resolves to Left, the traversal stops early and returns that Left.
Each step preserves context context and emits telemetry spans, including nested spans when bound.
Examples
iex> is_positive = fn num ->
...> Funx.Monad.Effect.lift_predicate(num, fn x -> x > 0 end, fn x -> Integer.to_string(x) <> " is not positive" end)
...> end
iex> result = Funx.Monad.Effect.traverse([1, 2, 3], fn num -> is_positive.(num) end)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: [1, 2, 3]}
iex> result = Funx.Monad.Effect.traverse([1, -2, 3], fn num -> is_positive.(num) end)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{left: "-2 is not positive"}
Traverses a list with a function that returns Effect values, combining results
into a single Effect. Unlike traverse/2, this version accumulates all errors
rather than stopping at the first Left.
Each successful computation contributes to the final list of results.
If any computations fail, all errors are collected and returned as a single Left.
This function also manages telemetry trace context across all nested effects, ensuring that span relationships and trace IDs are preserved through the traversal.
Examples
iex> validate = fn n ->
...> Funx.Monad.Effect.lift_predicate(n, fn x -> x > 0 end, fn x -> Integer.to_string(x) <> " is not positive" end)
...> end
iex> result = Funx.Monad.Effect.traverse_a([1, -2, 3], validate)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{left: ["-2 is not positive"]}
iex> result = Funx.Monad.Effect.traverse_a([1, 2, 3], validate)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: [1, 2, 3]}
@spec validate( value, (value -> t(error, any())) | [(value -> t(error, any()))], Funx.Monad.Effect.Context.opts_or_context() ) :: t([error], value) when error: term(), value: term()
Validates a value using one or more validator functions, each returning an Effect.
If all validators succeed (Right), the original value is returned in a Right.
If any validator fails (Left), all errors are accumulated and returned as a single Left.
This function also manages telemetry trace context across all nested validations, ensuring that span relationships and trace IDs are preserved throughout.
Supports optional opts for span metadata (e.g. :span_name).
Examples
iex> validate_positive = fn x ->
...> Funx.Monad.Effect.lift_predicate(x, fn n -> n > 0 end, fn n -> "Value " <> Integer.to_string(n) <> " must be positive" end)
...> end
iex> validate_even = fn x ->
...> Funx.Monad.Effect.lift_predicate(x, fn n -> rem(n, 2) == 0 end, fn n -> "Value " <> Integer.to_string(n) <> " must be even" end)
...> end
iex> validators = [validate_positive, validate_even]
iex> result = Funx.Monad.Effect.validate(4, validators)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Right{right: 4}
iex> result = Funx.Monad.Effect.validate(3, validators)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{left: ["Value 3 must be even"]}
iex> result = Funx.Monad.Effect.validate(-3, validators)
iex> Funx.Monad.Effect.run(result)
%Funx.Monad.Either.Left{left: ["Value -3 must be positive", "Value -3 must be even"]}