Skip to content

Lenses

A lens is a function-like object that maps between two Model classes. didactic ships three subtypes, in increasing strictness:

type shape round-trip law
Mapping[A, B] A -> B none; one-way
Lens[A, B] A -> (B, complement) and (B, complement) -> A GetPut and PutGet
Iso[A, B] A -> B and B -> A total bijection

Defining an Iso

import didactic.api as dx


class FirstLast(dx.Model):
    given: str
    family: str


class LastFirst(dx.Model):
    family: str
    given: str


class SwapNameOrder(dx.Iso[FirstLast, LastFirst]):
    def forward(self, n: FirstLast) -> LastFirst:
        return LastFirst(family=n.family, given=n.given)

    def backward(self, n: LastFirst) -> FirstLast:
        return FirstLast(given=n.given, family=n.family)


iso = SwapNameOrder()
iso(FirstLast(given="Ada", family="Lovelace"))
# LastFirst(family='Lovelace', given='Ada')

The Iso is also callable: iso(x) is iso.forward(x).

Defining a Lens

For a transformation that loses information in the forward direction but stores enough on the side to recover it:

class WithComment(dx.Model):
    payload: str
    comment: str


class WithoutComment(dx.Model):
    payload: str


class StripComment(dx.Lens[WithComment, WithoutComment]):
    def forward(self, w: WithComment) -> tuple[WithoutComment, str]:
        return WithoutComment(payload=w.payload), w.comment

    def backward(self, view: WithoutComment, comment: str) -> WithComment:
        return WithComment(payload=view.payload, comment=comment)

Composing lenses

>> composes by chaining forward and backward through both lenses:

ab >> bc        # Lens[A, C]

Composition is associative; identity is the trivial Iso.

Verifying lens laws

dx.testing.verify_iso runs the iso round-trip law on Hypothesis samples:

from hypothesis import strategies as st

dx.testing.verify_iso(
    SwapNameOrder(),
    st.builds(FirstLast, given=st.text(), family=st.text()),
)

For a non-iso lens, use check_lens_laws which runs GetPut. The complementary helpers verify_mapping, verify_lens_composition, verify_iso_inverse, and verify_migration_round_trip cover the rest of the algebraic laws.

Schema-parametric lenses

For a lens family that operates on many concrete schemas (Avro to OpenAPI conversion, for instance), use didactic.api.DependentLens. It wraps panproto's ProtolensChain and produces a concrete Lens once instantiated against a pair of schemas. See the reference page for the full surface.