| Safe Haskell | None |
|---|---|
| Language | GHC2024 |
Circuit.Optic
Description
Mixed optics as residual maps.
In the equipment-optics story an optic between two spans with common
boundaries is a globular 2-cell between the corresponding loose arrows. In
Prof that unwinds to the mixed-optic coend
Optic_M((S,R),(A,B)) = ∫^M C(S, M ⊙ A) × D(M ⊙ B, R)
where ⊙ is a monoidal action. In circuits the action is the tensor t
itself. Optic is the integrand — the residual ch is in the type, so
this is the residual-remembering rung. SomeOptic hides it, which is the
coend without the quotient.
Relationship to the rest of the library
Optic is the curried form of iomap: an optic is exactly a
pair of actions on channel poles, prefixIn on the conjoint
and suffixOut on the companion. opticPoles is that
identification, and it costs nothing to state.
The objects of the optic category are boundary pairs, which the local
Category class cannot index directly. Circuit.Poly
already solves that problem: packages a boundary
pair as a single Mono i oPoly, and instance Category Morphism is the wiki's
"category for free". So this module does not duplicate that instance; it
maps into it, with opticAsLens and lensAsOptic.
Constraints
Only identityOptic needs Unital, for the unitors. Composition and the
update action need nothing beyond Strength, because
— a
coherence the strength f == tensor id fAxioma.Circ oracles check at (,), Either and These.
This matters for base arrows that are premonoidal and therefore have
Strength but deliberately no Tensor instance, such as Circuit.Prob.
Synopsis
- data Optic (t :: k -> k1 -> k2) (arr :: k2 -> k2 -> Type) (ch :: k) (a :: k1) (b :: k1) (s :: k2) (r :: k2) = Optic {
- opticForward :: arr s (t ch a)
- opticBackward :: arr (t ch b) r
- data SomeOptic (t :: k -> k1 -> k2) (arr :: k2 -> k2 -> Type) (a :: k1) (b :: k1) (s :: k2) (r :: k2) where
- withSomeOptic :: forall {k1} {k2} {k3} (t :: k1 -> k2 -> k3) (arr :: k3 -> k3 -> Type) (a :: k2) (b :: k2) (s :: k3) (r :: k3) x. SomeOptic t arr a b s r -> (forall (ch :: k1). Optic t arr ch a b s r -> x) -> x
- identityOptic :: forall {k} (t :: k -> k -> k) (arr :: k -> k -> Type) (a :: k) (b :: k). Unital t arr => Optic t arr (Unit t) a b a b
- composeOptic :: forall {k} (t :: k -> k -> k) (arr :: k -> k -> Type) (ch2 :: k) (u :: k) (v :: k) (a :: k) (b :: k) (ch1 :: k) (s :: k) (r :: k). Strength t arr => Optic t arr ch2 u v a b -> Optic t arr ch1 a b s r -> Optic t arr (t ch1 ch2) u v s r
- identitySomeOptic :: forall {k} (t :: k -> k -> k) (arr :: k -> k -> Type) (a :: k) (b :: k). Unital t arr => SomeOptic t arr a b a b
- composeSomeOptic :: forall {k} (t :: k -> k -> k) (arr :: k -> k -> Type) (u :: k) (v :: k) (a :: k) (b :: k) (s :: k) (r :: k). Strength t arr => SomeOptic t arr u v a b -> SomeOptic t arr a b s r -> SomeOptic t arr u v s r
- opticUpdate :: forall {k} (t :: k -> k -> k) arr (ch :: k) (a :: k) (b :: k) (s :: k) (r :: k). Strength t arr => Optic t arr ch a b s r -> arr a b -> arr s r
- someOpticUpdate :: forall {k} (t :: k -> k -> k) arr (a :: k) (b :: k) (s :: k) (r :: k). Strength t arr => SomeOptic t arr a b s r -> arr a b -> arr s r
- opticPoles :: forall {k1} {k2} {k3} (arr :: k1 -> k1 -> Type) (t :: k2 -> k3 -> k1) (ch :: k2) (a :: k3) (b :: k3) (s :: k1) (r :: k1). Category arr => Optic t arr ch a b s r -> Poles arr (t ch a) (t ch b) -> Poles arr s r
- opticAsLens :: SomeOptic (,) (->) a b s r -> Morphism (Mono r s) (Mono b a)
- lensAsOptic :: Morphism (Mono r s) (Mono b a) -> Optic (,) (->) (b -> r) a b s r
Mixed optic
data Optic (t :: k -> k1 -> k2) (arr :: k2 -> k2 -> Type) (ch :: k) (a :: k1) (b :: k1) (s :: k2) (r :: k2) Source #
A mixed optic from (s,r) to (a,b) with residual ch.
opticForward :: arr s (t ch a)splits the domain left boundary into the residual and the codomain left boundary.opticBackward :: arr (t ch b) rrecombines the residual with the codomain right boundary.
For t = (,) and arr = (->) this is the concrete lens pair
s -> (ch, a) and (ch, b) -> r. For t = it is a prism: the
residual is the branch that did not match.Either
Constructors
| Optic | |
Fields
| |
data SomeOptic (t :: k -> k1 -> k2) (arr :: k2 -> k2 -> Type) (a :: k1) (b :: k1) (s :: k2) (r :: k2) where Source #
A mixed optic with the residual existentially hidden.
There is no residual value here, only a residual type: the forward leg
produces the residual and the backward leg consumes it. This is why
SomeOptic is cheaper than SomeBody, which must store a
seed and therefore has no inhabitant at tensors with an uninhabited unit.
identitySomeOptic exists at Either, where and the corresponding Unit
Either = VoidSomeBody identity does
not.
withSomeOptic :: forall {k1} {k2} {k3} (t :: k1 -> k2 -> k3) (arr :: k3 -> k3 -> Type) (a :: k2) (b :: k2) (s :: k3) (r :: k3) x. SomeOptic t arr a b s r -> (forall (ch :: k1). Optic t arr ch a b s r -> x) -> x Source #
Eliminator for the existential residual type.
Composition
identityOptic :: forall {k} (t :: k -> k -> k) (arr :: k -> k -> Type) (a :: k) (b :: k). Unital t arr => Optic t arr (Unit t) a b a b Source #
composeOptic :: forall {k} (t :: k -> k -> k) (arr :: k -> k -> Type) (ch2 :: k) (u :: k) (v :: k) (a :: k) (b :: k) (ch1 :: k) (s :: k) (r :: k). Strength t arr => Optic t arr ch2 u v a b -> Optic t arr ch1 a b s r -> Optic t arr (t ch1 ch2) u v s r Source #
Vertical composition of mixed optics.
Given opt1 from (s,r) to (a,b) with residual ch1 and opt2 from
(a,b) to (u,v) with residual ch2, the composite has residual
t ch1 ch2 — the tensoring of residuals in the coend formula. The
residual order matches cascadeBody: first-applied on the
left.
Note what is absent: composition reassociates and applies strength, but
never slide. cascadeBody needs two slides,
because a single Body must push one carrier past the payload
so that one arrow sees both. An optic keeps its two residuals on the same
side throughout. That is the precise sense in which body composition is the
fused case of optic composition.
Unit and associativity hold only up to the residual unitor and associator,
exactly as for Circ; the observational statements are in
Axioma.Optic.
>>>opticUpdate (composeOptic inner outer) (+ 1) ((3, True), "hi")((4,True),"hi")
The same result by nesting the updates — functoriality of opticUpdate:
>>>opticUpdate outer (opticUpdate inner (+ 1)) ((3, True), "hi")((4,True),"hi")
identitySomeOptic :: forall {k} (t :: k -> k -> k) (arr :: k -> k -> Type) (a :: k) (b :: k). Unital t arr => SomeOptic t arr a b a b Source #
identityOptic with the residual hidden.
composeSomeOptic :: forall {k} (t :: k -> k -> k) (arr :: k -> k -> Type) (u :: k) (v :: k) (a :: k) (b :: k) (s :: k) (r :: k). Strength t arr => SomeOptic t arr u v a b -> SomeOptic t arr a b s r -> SomeOptic t arr u v s r Source #
composeOptic with the residuals hidden. Once hidden, the bracketing
that makes composeOptic associative only up to the associator is no longer
observable in the type.
Action on morphisms
opticUpdate :: forall {k} (t :: k -> k -> k) arr (ch :: k) (a :: k) (b :: k) (s :: k) (r :: k). Strength t arr => Optic t arr ch a b s r -> arr a b -> arr s r Source #
Apply an optic to a plain base-arrow morphism.
A lens turns a focus-update into a whole-update; a prism turns a branch-update into a sum-update.
>>>opticUpdate firstLens (+ 1) (3, "hello")(4,"hello")
>>>(opticUpdate prismLeft (+ 1) (Left 3), opticUpdate prismLeft (+ 1) (Right "hi"))(Left 4,Right "hi")
Lawfulness is not enforced. is the round-trip condition, and a well-typed optic
can fail it; opticUpdate o id ==
idAxioma.Optic carries a witness that it can.
someOpticUpdate :: forall {k} (t :: k -> k -> k) arr (a :: k) (b :: k) (s :: k) (r :: k). Strength t arr => SomeOptic t arr a b s r -> arr a b -> arr s r Source #
opticUpdate through the existential.
Action on channel poles
opticPoles :: forall {k1} {k2} {k3} (arr :: k1 -> k1 -> Type) (t :: k2 -> k3 -> k1) (ch :: k2) (a :: k3) (b :: k3) (s :: k1) (r :: k1). Category arr => Optic t arr ch a b s r -> Poles arr (t ch a) (t ch b) -> Poles arr s r Source #
The action of an optic on channel poles.
This is iomap with its two arguments read as the legs of an
optic: opticForward prefixes the conjoint, opticBackward suffixes the
companion. Since Circuit.Poles already describes that pair as "the left
action of arr on In poles" and "the right action of arr on Out
poles", an optic is a morphism of that enriched profunctor.
>>>let p = poles0 (const ()) (const ("hi", 7)) :: Poles (->) (String, Int) (String, Int)>>>snd (splay0 (opticPoles firstLens p)) ()(7,"hi")
Bridge to the polynomial lens
opticAsLens :: SomeOptic (,) (->) a b s r -> Morphism (Mono r s) (Mono b a) Source #
A cartesian optic as a polynomial lens.
Currying the residual away turns the pair s -> (ch, a), (ch, b) -> r
into s -> (a, b -> r), which is exactly
.applyLens :: Morphism (Mono r s) (Mono b a) -> s -> (a, b -> r)
>>>let (a, put) = applyLens (opticAsLens (SomeOptic firstLens)) (3, "hello") in (a, put 9)(3,(9,"hello"))
lensAsOptic :: Morphism (Mono r s) (Mono b a) -> Optic (,) (->) (b -> r) a b s r Source #
A polynomial lens as a cartesian optic.
The residual is reconstructed as the continuation type b -> r — the
classical "existential is a function" encoding. So changes the residual and is only an identity after the
coend quotient; lensAsOptic
. opticAsLensAxioma.Optic checks that it is an identity
observationally, which is that quotient in action.