defprotocol Funx.Monad do @moduledoc """ The `Funx.Monad` protocol defines the core monadic operations: `ap/2`, `bind/2`, and `map/2`. A monad is an abstraction that represents computations as a series of steps. This protocol is designed to be implemented by types that wrap a value and allow chaining of operations while preserving the wrapped context. ## Functions - `map/2`: Applies a function to the value within the monad. - `bind/2`: Chains operations by passing the unwrapped value into a function that returns another monad. - `ap/2`: Applies a monadic function to another monadic value. """ @fallback_to_any true @type t() :: term() @doc """ Applies a monadic function to another monadic value. The function `func` is expected to be wrapped in a monadic context and is applied to the value `m` within its own monadic context. The result is wrapped in the same context as the original monad. ## Examples iex> Funx.Monad.ap(Funx.Monad.Maybe.just(fn x -> x * 2 end), Funx.Monad.Maybe.just(3)) %Funx.Monad.Maybe.Just{value: 6} In the case of `Nothing`: iex> Funx.Monad.ap(Funx.Monad.Maybe.nothing(), Funx.Monad.Maybe.just(3)) %Funx.Monad.Maybe.Nothing{} """ @spec ap(t(), t()) :: t() def ap(monadic_func, monad_value) @doc """ Chains a monadic operation. The `bind/2` function takes a monad `m` and a function `func`. The function `func` is applied to the unwrapped value of `m`, and must return another monad. The result is the new monad produced by `func`. This is the core operation that allows chaining of computations, with the value being passed from one function to the next in a sequence. ## Examples iex> Funx.Monad.bind(Funx.Monad.Maybe.just(5), fn x -> Funx.Monad.Maybe.just(x * 2) end) %Funx.Monad.Maybe.Just{value: 10} In the case of `Nothing`: iex> Funx.Monad.bind(Funx.Monad.Maybe.nothing(), fn _ -> Funx.Monad.Maybe.just(5) end) %Funx.Monad.Maybe.Nothing{} """ @spec bind(t(), (term() -> t())) :: t() def bind(monad_value, func_returning_monad) @doc """ Maps a function over the value inside the monad. The `map/2` function takes a monad `m` and a function `func`, applies the function to the value inside `m`, and returns a new monad containing the result. The original monadic context is preserved. ## Examples iex> Funx.Monad.map(Funx.Monad.Maybe.just(2), fn x -> x + 3 end) %Funx.Monad.Maybe.Just{value: 5} In the case of `Nothing`: iex> Funx.Monad.map(Funx.Monad.Maybe.nothing(), fn x -> x + 3 end) %Funx.Monad.Maybe.Nothing{} """ @spec map(t(), (term() -> term())) :: t() def map(monad_value, func) end defimpl Funx.Monad, for: Any do @spec ap(any(), any()) :: any() def ap(func, value) when is_function(func, 1), do: func.(value) @spec bind(any(), (any() -> any())) :: any() def bind(value, func) when is_function(func, 1), do: func.(value) @spec map(any(), (any() -> any())) :: any() def map(value, func) when is_function(func, 1), do: func.(value) end