Current section
Files
Jump to
Current section
Files
lib/monad/maybe/dsl/behaviour.ex
defmodule Funx.Monad.Maybe.Dsl.Behaviour do
@moduledoc """
Behaviour for modules that participate in the Maybe DSL.
A module implementing this behaviour must define `run_maybe/3`. The DSL calls
`run_maybe/3` with the current value, any options given alongside the module
inside the DSL, and the global environment provided to the `maybe` macro.
How the return value is treated depends on whether the module is used with
`bind` or `map`.
## Examples
An operation that may return nothing:
iex> defmodule MyParseInt do
...> use Funx.Monad.Maybe
...> @behaviour Funx.Monad.Maybe.Dsl.Behaviour
...>
...> @impl true
...> def run_maybe(value, opts, _env) when is_binary(value) do
...> base = Keyword.get(opts, :base, 10)
...>
...> case Integer.parse(value, base) do
...> {int, ""} -> just(int)
...> _ -> nothing()
...> end
...> end
...> end
iex> MyParseInt.run_maybe("42", [], [])
%Funx.Monad.Maybe.Just{value: 42}
iex> MyParseInt.run_maybe("FF", [base: 16], [])
%Funx.Monad.Maybe.Just{value: 255}
iex> MyParseInt.run_maybe("invalid", [], [])
%Funx.Monad.Maybe.Nothing{}
A pure transformation:
iex> defmodule MyDouble do
...> @behaviour Funx.Monad.Maybe.Dsl.Behaviour
...>
...> @impl true
...> def run_maybe(value, _opts, _env) when is_number(value) do
...> value * 2
...> end
...> end
iex> MyDouble.run_maybe(21, [], [])
42
A validator that uses options:
iex> defmodule MyPositiveNumber do
...> use Funx.Monad.Maybe
...> @behaviour Funx.Monad.Maybe.Dsl.Behaviour
...>
...> @impl true
...> def run_maybe(value, opts, _env) do
...> min = Keyword.get(opts, :min, 0)
...>
...> if value > min do
...> just(value)
...> else
...> nothing()
...> end
...> end
...> end
iex> MyPositiveNumber.run_maybe(10, [], [])
%Funx.Monad.Maybe.Just{value: 10}
iex> MyPositiveNumber.run_maybe(100, [min: 50], [])
%Funx.Monad.Maybe.Just{value: 100}
iex> MyPositiveNumber.run_maybe(-5, [min: 0], [])
%Funx.Monad.Maybe.Nothing{}
## Usage in the DSL
iex> defmodule DslParseInt do
...> use Funx.Monad.Maybe
...> @behaviour Funx.Monad.Maybe.Dsl.Behaviour
...> @impl true
...> def run_maybe(value, _opts, _env) when is_binary(value) do
...> case Integer.parse(value) do
...> {int, ""} -> just(int)
...> _ -> nothing()
...> end
...> end
...> end
iex> defmodule DslPositive do
...> use Funx.Monad.Maybe
...> @behaviour Funx.Monad.Maybe.Dsl.Behaviour
...> @impl true
...> def run_maybe(value, opts, _env) do
...> min = Keyword.get(opts, :min, 0)
...> if value > min, do: just(value), else: nothing()
...> end
...> end
iex> defmodule DslDouble do
...> @behaviour Funx.Monad.Maybe.Dsl.Behaviour
...> @impl true
...> def run_maybe(value, _opts, _env), do: value * 2
...> end
iex> use Funx.Monad.Maybe
iex> maybe "42" do
...> bind DslParseInt
...> bind {DslPositive, min: 10}
...> map DslDouble
...> end
%Funx.Monad.Maybe.Just{value: 84}
`bind` unwraps the current Maybe value, calls `run_maybe/3`, and normalizes the
result back into Maybe. `map` unwraps the value, calls `run_maybe/3`, and wraps the
plain result back into Maybe.
"""
@doc """
Runs a single step in a Maybe pipeline.
Arguments:
* value
The current value provided by the pipeline.
* opts
Module-specific options passed in the DSL, for example:
bind ParseInt, base: 16
* env
The read-only environment supplied by the `maybe` macro. It is threaded
through the pipeline unchanged.
Return expectations:
* With `bind`, `run_maybe/3` may return a Maybe or a result tuple.
* With `map`, `run_maybe/3` should return a plain value.
Examples:
# Suitable for bind
def run_maybe(value, _opts, _env) do
if valid?(value), do: just(value), else: nothing()
end
# Suitable for map
def run_maybe(value, opts, _env) do
value * Keyword.get(opts, :multiplier, 2)
end
# Returning a result tuple (also suitable for bind)
def run_maybe(value, _opts, _env) do
case process(value) do
{:ok, result} -> {:ok, result}
error -> error
end
end
"""
@callback run_maybe(value :: any(), opts :: keyword(), env :: keyword()) ::
any()
| Funx.Monad.Maybe.t(any())
| {:ok, any()}
| {:error, any()}
end