defmodule Funx.Monad.Reader do @moduledoc """ The `Funx.Monad.Reader` module represents the Reader monad, which allows computations to access shared, read-only environment values. This module defines core Reader functions: * `pure/1` – Lifts a value into the Reader context. * `run/2` – Executes the Reader with a given environment. * `asks/1` – Extracts and transforms a value from the environment. * `ask/0` – Extracts the full environment. This module implements the following protocol: * `Funx.Monad`: Implements `bind/2`, `map/2`, and `ap/2` for monadic composition. Note: The Reader monad does not implement `Eq` or `Ord`, since Readers are lazy— they do not actually contain a value until they are run. We only can compare the results of a Reader, not the Reader itself. """ @type t(env, value) :: %__MODULE__{run: (env -> value)} @enforce_keys [:run] defstruct [:run] @doc """ Lifts a value into the `Reader` context. ## Examples iex> reader = Funx.Monad.Reader.pure(42) iex> Funx.Monad.Reader.run(reader, %{}) 42 """ @spec pure(value :: A) :: t(any(), A) when A: var def pure(value), do: %__MODULE__{run: fn _env -> value end} @doc """ Runs the `Reader` with the provided environment, returning the computed value. ## Examples iex> reader = Funx.Monad.Reader.pure(42) iex> Funx.Monad.Reader.run(reader, %{}) 42 """ @spec run(t(Env, A), Env) :: A when Env: var, A: var def run(%__MODULE__{run: f}, env), do: f.(env) @doc """ Extracts and transforms the value contained in the environment, making it available within the Reader context. ## Examples iex> reader = Funx.Monad.Reader.asks(fn env -> Map.get(env, :foo) end) iex> Funx.Monad.Reader.run(reader, %{foo: "bar"}) "bar" """ @spec asks(func :: (Env -> A)) :: t(Env, A) when Env: var, A: var def asks(func), do: %__MODULE__{run: func} @doc """ Extracts the value contained in the environment, making it available within the Reader context. ## Examples iex> reader = Funx.Monad.Reader.ask() iex> Funx.Monad.Reader.run(reader, %{foo: "bar"}) %{foo: "bar"} """ @spec ask() :: t(Env, Env) when Env: var def ask, do: asks(fn env -> env end) end defimpl Funx.Monad, for: Funx.Monad.Reader do alias Funx.Monad.Reader @spec map(Reader.t(Env, A), (A -> B)) :: Reader.t(Env, B) when Env: var, A: var, B: var def map(%Reader{run: f}, func), do: %Reader{run: fn env -> func.(f.(env)) end} @spec bind(Reader.t(Env, A), (A -> Reader.t(Env, B))) :: Reader.t(Env, B) when Env: var, A: var, B: var def bind(%Reader{run: f}, func), do: %Reader{run: fn env -> func.(f.(env)).run.(env) end} @spec ap(Reader.t(Env, (A -> B)), Reader.t(Env, A)) :: Reader.t(Env, B) when Env: var, A: var, B: var def ap(%Reader{run: f_func}, %Reader{run: f_value}), do: %Reader{run: fn env -> f_func.(env).(f_value.(env)) end} end