Current section

Files

Jump to
funx lib monoid optics prism_compose.ex
Raw

lib/monoid/optics/prism_compose.ex

defmodule Funx.Monoid.Optics.PrismCompose do
@moduledoc """
The `Funx.Monoid.Optics.PrismCompose` module provides a monoid wrapper for sequential prism composition.
This wrapper allows prisms to be used with generic monoid operations like `m_concat/2` and `m_append/3`,
enabling functional composition of multiple prisms into a single partial focusing operation.
### Wrapping and Unwrapping
- `new/1`: Wraps a prism in a `PrismCompose` monoid.
- `unwrap/1`: Extracts the prism from a `PrismCompose` wrapper.
### Monoid Operations (via protocol)
- `empty/1`: Returns the identity prism (accepts all values).
- `append/2`: Composes two prisms sequentially (outer then inner).
- `wrap/2`: Wraps a prism value into the monoid.
## Examples
iex> alias Funx.Monoid.Optics.PrismCompose
iex> alias Funx.Optics.Prism
iex> prisms = [
...> Prism.key(:account),
...> Prism.key(:name)
...> ]
iex> wrapped = Enum.map(prisms, &PrismCompose.new/1)
iex> composed = Funx.Monoid.Utils.m_concat(%PrismCompose{}, prisms)
iex> Prism.preview(%{account: %{name: "Alice"}}, composed)
%Funx.Monad.Maybe.Just{value: "Alice"}
"""
alias Funx.Optics.Prism
@type t :: %__MODULE__{
prism: Prism.t()
}
defstruct prism: nil
@doc """
Wraps a prism in a PrismCompose monoid.
"""
@spec new(Prism.t()) :: t()
def new(%Prism{} = prism), do: %__MODULE__{prism: prism}
@doc """
Extracts the prism from a PrismCompose wrapper.
"""
@spec unwrap(t()) :: Prism.t()
def unwrap(%__MODULE__{prism: prism}), do: prism
end
defimpl Funx.Monoid, for: Funx.Monoid.Optics.PrismCompose do
alias Funx.Monoid.Optics.PrismCompose
alias Funx.Optics.Prism
@doc """
Returns the identity prism (accepts all values).
"""
def empty(_) do
PrismCompose.new(Prism.identity())
end
@doc """
Composes two prisms sequentially.
The outer prism runs first; if it succeeds, the inner prism runs next.
This is the canonical implementation of prism composition.
"""
def append(%PrismCompose{prism: outer}, %PrismCompose{prism: inner}) do
import Funx.Monad, only: [bind: 2]
composed =
Prism.make(
fn s ->
Prism.preview(s, outer)
|> bind(fn i -> Prism.preview(i, inner) end)
end,
fn a ->
inner_value = Prism.review(a, inner)
Prism.review(inner_value, outer)
end
)
PrismCompose.new(composed)
end
@doc """
Wraps a prism value into a PrismCompose monoid.
"""
def wrap(_monoid, %Prism{} = prism) do
PrismCompose.new(prism)
end
@doc """
Extracts the prism from the PrismCompose wrapper.
"""
def unwrap(%PrismCompose{prism: prism}), do: prism
end