Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Protolens composition

In plain terms

A protolens is a lens recipe parameterised over a schema rather than a fixed pair of schemas. Where a lens lives between two specific schemas and , a protolens is a rule that says “for any schema satisfying some precondition, here is a lens from to a derived schema .” Applying a protolens to a fleet of related schemas is one operation; you do not write one lens per schema.

Composing protolenses requires more care than composing plain lenses. Two protolenses can be glued together end-to-end only when the schema produced by the first is precisely the schema consumed by the second. “Precisely” here is structural: the intermediate schemas have to agree as endofunctors on theories, not just on names. This page pins down that condition.

Semantic domain

A protolens is a natural transformation between schema endofunctors. Concretely:

where and are functors on the category of schemas , and for each schema , is a lens

satisfying the lens laws (see Lens DSL).

The naturality condition is: for every schema morphism , the square

commutes: . Applying the protolens then transporting along gives the same result as transporting then applying the protolens.

Composition

Two protolenses and compose vertically into pointwise, by :

The composition is well-defined only when the intermediate functor of matches the source functor of . In panproto-lens, this match is checked by protolens_composable:

pub fn protolens_composable(eta: &Protolens, theta: &Protolens) -> bool {
    matches!(theta.source.transform, TheoryTransform::Identity)
        || theory_endofunctor_equiv(&eta.target, &theta.source)
}

A Protolens carries its source and target TheoryEndofunctors as public fields. The equality theory_endofunctor_equiv is structural: the endofunctors agree iff their preconditions and their transforms agree, ignoring the human-readable name field. A trivial special case: when the source of is the identity functor (i.e., is the identity), the match is automatic regardless of ’s target.

vertical_compose enforces the check at construction time and returns LensError::CompositionMismatch on failure, naming the offending intermediate functor. This catches an entire class of bug at the type-construction stage rather than at instantiation time.

Sequential vs fused instantiation

When a chain of composable protolenses is applied to a base schema , two instantiation strategies are exposed:

  • Fused instantiation (instantiate): construct the composed protolens first, then apply it to once. Produces a single morphism with the migration metadata preserved as one chain.
  • Sequential instantiation (instantiate_sequential): apply to to get , apply to to get , and so on. Produces a list of morphisms.

Both satisfy the lens laws. Fused is the production default because it preserves migration metadata as a single object; sequential is exposed for property tests that need to inspect each intermediate schema.

Soundness

The composition operation preserves naturality: if and are natural transformations and protolens_composable(P, Q) holds, then is a natural transformation. The pointwise lens laws follow from the laws on each and .

The structural-equality check on intermediate functors is the necessary condition for the composite to be well-defined. Without it, vertical composition could be invoked with mismatched functors and the result would silently fail naturality on some schemas. The check makes such composition a runtime error at construction time rather than a semantic bug at application time.

What is intentionally not modelled

  • Horizontal composition of protolenses. Naturality also supports horizontal composition (whiskering), but the implementation does not currently expose it. Adding it would require a notion of natural transformation between protolens-shaped functors, which is not yet defined in the codebase.
  • Identity-sourced protolens equivalence beyond structural. Two protolenses that compute the same transform via different intermediate forms are treated as distinct.
  • Performance characteristics of fused vs sequential. The choice is semantic-equivalent; fused is preferred for metadata reasons, not performance.

See also