Current section
Files
Jump to
Current section
Files
lib/monoid/optics/iso_compose.ex
defmodule Funx.Monoid.Optics.IsoCompose do
@moduledoc """
The `Funx.Monoid.Optics.IsoCompose` module provides a monoid wrapper for sequential iso composition.
This wrapper allows isos to be used with generic monoid operations like `m_concat/2` and `m_append/3`,
enabling functional composition of multiple isos into a single bidirectional transformation.
### Wrapping and Unwrapping
- `new/1`: Wraps an iso in an `IsoCompose` monoid.
- `unwrap/1`: Extracts the iso from an `IsoCompose` wrapper.
### Monoid Operations (via protocol)
- `empty/1`: Returns the identity iso (both directions are identity).
- `append/2`: Composes two isos sequentially.
- `wrap/2`: Wraps an iso value into the monoid.
## Examples
iex> alias Funx.Monoid.Optics.IsoCompose
iex> alias Funx.Optics.Iso
iex> isos = [
...> Iso.make(
...> fn s -> String.to_integer(s) end,
...> fn i -> Integer.to_string(i) end
...> ),
...> Iso.make(
...> fn i -> i * 2 end,
...> fn i -> div(i, 2) end
...> )
...> ]
iex> composed = Funx.Monoid.Utils.m_concat(%IsoCompose{}, isos)
iex> Iso.view("21", composed)
42
"""
alias Funx.Optics.Iso
@type t :: %__MODULE__{
iso: Iso.t()
}
defstruct iso: nil
@doc """
Wraps an iso in an IsoCompose monoid.
"""
@spec new(Iso.t()) :: t()
def new(%Iso{} = iso), do: %__MODULE__{iso: iso}
@doc """
Extracts the iso from an IsoCompose wrapper.
"""
@spec unwrap(t()) :: Iso.t()
def unwrap(%__MODULE__{iso: iso}), do: iso
end
defimpl Funx.Monoid, for: Funx.Monoid.Optics.IsoCompose do
alias Funx.Monoid.Optics.IsoCompose
alias Funx.Optics.Iso
@doc """
Returns the identity iso (both directions are identity functions).
"""
def empty(_) do
IsoCompose.new(Iso.identity())
end
@doc """
Composes two isos sequentially.
The outer iso transforms first, then the inner iso transforms the result.
Forward direction: applies outer's view first, then inner's view
Backward direction: applies inner's review first, then outer's review
"""
def append(%IsoCompose{iso: outer}, %IsoCompose{iso: inner}) do
composed =
Iso.make(
fn s ->
s |> Iso.view(outer) |> Iso.view(inner)
end,
fn a ->
a |> Iso.review(inner) |> Iso.review(outer)
end
)
IsoCompose.new(composed)
end
@doc """
Wraps an iso value into an IsoCompose monoid.
"""
def wrap(_monoid, %Iso{} = iso) do
IsoCompose.new(iso)
end
@doc """
Extracts the iso from the IsoCompose wrapper.
"""
def unwrap(%IsoCompose{iso: iso}), do: iso
end