View Source Funx.Monad.Either.Dsl.Behaviour behaviour (funx v0.3.0)
Behaviour for modules that participate in the Either DSL.
A module implementing this behaviour must define run/3. The DSL calls
run/3 with the current value, the global environment provided to the
either macro, and any options given alongside the module inside the DSL.
How the return value is treated depends on whether the module is used with
bind, map, or run.
Examples
An operation that may fail:
iex> defmodule MyParseInt do
...> use Funx.Monad.Either
...> @behaviour Funx.Monad.Either.Dsl.Behaviour
...>
...> @impl true
...> def run(value, _env, opts) when is_binary(value) do
...> base = Keyword.get(opts, :base, 10)
...>
...> case Integer.parse(value, base) do
...> {int, ""} -> right(int)
...> _ -> left("Invalid integer")
...> end
...> end
...> end
iex> MyParseInt.run("42", [], [])
%Funx.Monad.Either.Right{right: 42}
iex> MyParseInt.run("FF", [], [base: 16])
%Funx.Monad.Either.Right{right: 255}
iex> MyParseInt.run("invalid", [], [])
%Funx.Monad.Either.Left{left: "Invalid integer"}A pure transformation:
iex> defmodule MyDouble do
...> @behaviour Funx.Monad.Either.Dsl.Behaviour
...>
...> @impl true
...> def run(value, _env, _opts) when is_number(value) do
...> value * 2
...> end
...> end
iex> MyDouble.run(21, [], [])
42A validator that uses options:
iex> defmodule MyPositiveNumber do
...> use Funx.Monad.Either
...> @behaviour Funx.Monad.Either.Dsl.Behaviour
...>
...> @impl true
...> def run(value, _env, opts) do
...> min = Keyword.get(opts, :min, 0)
...>
...> if value > min do
...> right(value)
...> else
...> left("must be > #{min}, got: #{value}")
...> end
...> end
...> end
iex> MyPositiveNumber.run(10, [], [])
%Funx.Monad.Either.Right{right: 10}
iex> MyPositiveNumber.run(100, [], [min: 50])
%Funx.Monad.Either.Right{right: 100}
iex> MyPositiveNumber.run(-5, [], [min: 0])
%Funx.Monad.Either.Left{left: "must be > 0, got: -5"}Usage in the DSL
iex> defmodule DslParseInt do
...> use Funx.Monad.Either
...> @behaviour Funx.Monad.Either.Dsl.Behaviour
...> @impl true
...> def run(value, _env, _opts) when is_binary(value) do
...> case Integer.parse(value) do
...> {int, ""} -> right(int)
...> _ -> left("Invalid integer")
...> end
...> end
...> end
iex> defmodule DslPositive do
...> use Funx.Monad.Either
...> @behaviour Funx.Monad.Either.Dsl.Behaviour
...> @impl true
...> def run(value, _env, opts) do
...> min = Keyword.get(opts, :min, 0)
...> if value > min, do: right(value), else: left("too small")
...> end
...> end
iex> defmodule DslDouble do
...> @behaviour Funx.Monad.Either.Dsl.Behaviour
...> @impl true
...> def run(value, _env, _opts), do: value * 2
...> end
iex> use Funx.Monad.Either
iex> either "42" do
...> bind DslParseInt
...> bind {DslPositive, min: 10}
...> map DslDouble
...> end
%Funx.Monad.Either.Right{right: 84}bind unwraps the current Either value, calls run/3, and normalizes the
result back into Either. map unwraps the value, calls run/3, and wraps the
plain result back into Either.
Summary
Callbacks
Runs a single step in an Either pipeline.
Callbacks
@callback run(value :: any(), env :: keyword(), opts :: keyword()) :: any() | Funx.Monad.Either.t(any(), any()) | {:ok, any()} | {:error, any()}
Runs a single step in an Either pipeline.
Arguments:
value The current value provided by the pipeline.
env The read-only environment supplied by the
eithermacro. It is threaded through the pipeline unchanged.opts Module-specific options passed in the DSL, for example:
bind ParseInt, base: 16
Return expectations:
- With
bind,run/3may return an Either or a result tuple. - With
map,run/3should return a plain value.
Examples:
# Suitable for bind
def run(value, _env, _opts) do
if valid?(value), do: right(value), else: left("invalid")
end
# Suitable for map
def run(value, _env, opts) do
value * Keyword.get(opts, :multiplier, 2)
end
# Returning a result tuple (also suitable for bind)
def run(value, _env, _opts) do
case process(value) do
{:ok, result} -> {:ok, result}
error -> error
end
end