Funx.Monad.Maybe.Dsl (funx v0.8.4)
View SourceProvides the maybe/2 macro for writing declarative pipelines in the Maybe context.
The DSL lets you express a sequence of operations that may return nothing without manually
threading values through bind, map, or filter. Input is lifted into Maybe
automatically, each step runs in order, and the pipeline stops on the first Nothing.
Supported Operations
bind- for operations that return Maybe, Either, result tuples, or nilmap- for transformations that return plain valuesap- for applying a function in a Maybe to a value in a Maybe- Maybe functions:
or_else - Protocol functions:
tap(via Funx.Tappable),filter,filter_map,guard(via Funx.Filterable)
The result format is controlled by the :as option (:maybe, :raise, or :nil).
Short-Circuit Behavior
The DSL uses fail-fast semantics. When any step returns a Nothing value, Either.Left, {:error, _} tuple, or nil,
the pipeline stops immediately and returns Nothing. Subsequent steps are never executed.
Example:
iex> defmodule GetUser do
...> use Funx.Monad.Maybe
...> @behaviour Funx.Monad.Behaviour.Bind
...> def bind(_value, _opts, _env), do: nothing()
...> end
iex> defmodule CheckPermissions do
...> use Funx.Monad.Maybe
...> @behaviour Funx.Monad.Behaviour.Bind
...> def bind(value, _opts, _env), do: just(value)
...> end
iex> defmodule FormatUser do
...> @behaviour Funx.Monad.Behaviour.Map
...> def map(value, _opts, _env), do: "formatted: #{value}"
...> end
iex> use Funx.Monad.Maybe
iex> maybe 123 do
...> bind GetUser # Returns Nothing
...> bind CheckPermissions # Never runs
...> map FormatUser # Never runs
...> end
%Funx.Monad.Maybe.Nothing{}Performance
The DSL compiles to direct function calls at compile time. There is no runtime
overhead for the DSL itself - it expands into the same code you would write manually
with bind, map, etc.
Example showing compile-time expansion:
iex> defmodule ParseInt do
...> use Funx.Monad.Maybe
...> @behaviour Funx.Monad.Behaviour.Bind
...> def bind(value, _opts, _env) when is_binary(value) do
...> case Integer.parse(value) do
...> {int, ""} -> just(int)
...> _ -> nothing()
...> end
...> end
...> end
iex> defmodule Double do
...> @behaviour Funx.Monad.Behaviour.Map
...> def map(value, _opts, _env), do: value * 2
...> end
iex> use Funx.Monad.Maybe
iex> maybe "42" do
...> bind ParseInt
...> map Double
...> end
%Funx.Monad.Maybe.Just{value: 84}Auto-lifting creates anonymous functions, but these are created at compile time, not runtime. For performance-critical hot paths, you may prefer direct combinator calls, but the difference is typically negligible.
Transformers
Transformers allow post-parse optimization and validation of pipelines:
maybe user_id, transformers: [MyCustomTransformer] do
bind GetUser
map Transform
endTransformers run at compile time and create compile-time dependencies.
Example
maybe user_id, as: :nil do
bind Accounts.get_user()
bind Policies.ensure_active()
map fn user -> %{user: user} end
endAuto-Lifting of Function Calls
The DSL automatically lifts certain function call patterns for convenience:
Module.fun()becomes&Module.fun/1(zero-arity qualified calls)Module.fun(arg)becomesfn x -> Module.fun(x, arg) end(partial application)
This is particularly useful in filter operations:
filter &Validator.positive?/1This module defines the public DSL entry point. The macro expansion details and internal rewrite rules are not part of the public API.