Funx.Monad.Behaviour.Ap behaviour (funx v0.8.0)
View SourceBehaviour for applicative operations across monad DSLs.
Ap (apply) is the applicative functor operation that produces a wrapped function to be applied to wrapped values. This allows for function application within a computational context.
Contract
@callback ap(value :: any(), opts :: keyword(), env :: keyword()) ::
{:ok, (any() -> any())}
| {:error, any()}
| Either.t((any() -> any()), any())
| Maybe.t((any() -> any()))Arguments
value- The value to use for producing the functionopts- Keyword list of options (module-specific configuration)env- Environment/context from DSL (for Reader-like dependency injection)
Return Values
Monad types (preferred):
Either.right(fn)- Success with a functionEither.left(error)- Failure with errorMaybe.just(fn)- Success with a functionMaybe.nothing()- Failure (no function)
Tagged tuples (supported):
{:ok, fn}- Operation succeeded with a function{:error, reason}- Operation failed with error
The DSL will normalize all these return values into the appropriate monad type.
Cross-Monad Normalization
When an ap module returns a monad type different from the current DSL context,
the result is automatically normalized:
Maybe → Either:
Just(fn)→Right(fn)Nothing→Left(:nothing)(uses:nothingatom as error)
Either → Maybe:
Right(fn)→Just(fn)Left(_error)→Nothing(error information is discarded)
Semantic Rules
- Arguments strictly ordered: value, opts, env
- May use env for Reader-like dependency injection (only way to access env - functions cannot)
- Produces a function - returns a wrapped function, not a wrapped value
- Can fail - use this for operations that might not produce a function
Examples
Basic Applicative
defmodule Multiplier do
@behaviour Funx.Monad.Behaviour.Ap
import Funx.Monad.Either
@impl true
def ap(factor, _opts, _env) when is_number(factor) do
right(fn x -> x * factor end)
end
def ap(_value, _opts, _env), do: left("Expected number for factor")
end
# Usage in Either DSL
use Funx.Monad.Either
either 5 do
ap Multiplier
end
# First evaluates Multiplier.ap(5, []) -> Right(fn x -> x * 5 end)
# Then applies: fn x -> x * 5 end to 5 -> 25
#=> %Right{right: 25}With Options
defmodule ConditionalOperator do
@behaviour Funx.Monad.Behaviour.Ap
import Funx.Monad.Either
@impl true
def ap(value, opts, _env) do
op = Keyword.get(opts, :operation, :add)
case op do
:add -> right(fn x -> x + value end)
:multiply -> right(fn x -> x * value end)
:subtract -> right(fn x -> x - value end)
_ -> left("Unknown operation: #{op}")
end
end
end
# Usage
either 10 do
ap {ConditionalOperator, operation: :multiply}
end
#=> %Right{right: 100}Failure Cases
defmodule ValidatedOperator do
@behaviour Funx.Monad.Behaviour.Ap
import Funx.Monad.Either
@impl true
def ap(value, _opts, _env) when is_number(value) and value > 0 do
right(fn x -> x + value end)
end
def ap(value, _opts, _env) when is_number(value) do
left("Factor must be positive, got: #{value}")
end
def ap(_value, _opts, _env), do: left("Expected number")
end
# Success
either 5 do
ap ValidatedOperator
end
#=> %Right{right: 10}
# Failure
either -3 do
ap ValidatedOperator
end
#=> %Left{left: "Factor must be positive, got: -3"}
Summary
Callbacks
Produces a wrapped function based on the input value.
Callbacks
@callback ap(value :: any(), opts :: keyword(), env :: keyword()) :: {:ok, (any() -> any())} | {:error, any()} | Funx.Monad.Either.t((any() -> any()), any()) | Funx.Monad.Maybe.t((any() -> any()))
Produces a wrapped function based on the input value.
Arguments:
- value - The current value in the pipeline
- opts - Module-specific options passed in the DSL
- env - Environment/context from the DSL (for dependency injection)
Returns a wrapped function that will be applied to the value.
Examples:
# Using Either
import Funx.Monad.Either
def ap(multiplier, _opts, _env) when is_number(multiplier) do
right(fn x -> x * multiplier end)
end
# Using tagged tuples
def ap(multiplier, _opts, _env) when is_number(multiplier) do
{:ok, fn x -> x * multiplier end}
end
# With failure case
def ap(value, _opts, _env) do
if valid?(value) do
right(fn x -> transform(x, value) end)
else
left("validation failed")
end
end
# With options
def ap(value, opts, _env) do
operation = Keyword.get(opts, :operation, :default)
right(create_function(operation, value))
end
# Using env for dependency injection
def ap(value, _opts, env) do
transformer = Keyword.get(env, :transformer)
right(fn x -> transformer.transform(x, value) end)
end