Iteration with optics

Posted on February 22, 2024

(X-posted on the CyberCat Institute blog)

In this post I’ll describe the theory of how to add iteration to categories of optics. Iteration is required for almost all applications of categorical cybernetics beyond game theory, and is something we’ve been handling only semi-formally for some time. The only tool we need is already one we have inside the categorical cybernetics framework: parametrisation weighted by a lax monoidal functor. I’ll end with a conjecture that this is an instance of a general procedure to force states in a symmetric monoidal category.

This post is strongly inspired by the account of Moore machines in David Jaz Myers’ book Categorical Systems Theory, and Matteo’s enthusiasm for it. There’s probably a big connection to things like Delayed trace categories, but I don’t understand it yet.

The diagrams in this post are made with Quiver and Tangle.

The iteration functor

For the purposes of this post, we’ll be working with a symmetric monoidal category 𝒞\mathcal C, and the category 𝐎𝐩𝐭𝐢𝐜(𝒞)\mathbf{Optic} (\mathcal C) of monoidal optics over it. Objects of 𝐎𝐩𝐭𝐢𝐜(𝒞)\mathbf{Optic} (\mathcal C) are pairs of objects of 𝒞\mathcal C, and morphisms are given by the coend formula

𝐎𝐩𝐭𝐢𝐜(𝒞)((XX),(YY))=M:𝒞𝒞(X,MY)×𝒞(MY,X) \mathbf{Optic} (\mathcal C) \left( \binom{X}{X'}, \binom{Y}{Y'} \right) = \int_{M : \mathcal C} \mathcal C (X, M \otimes Y) \times \mathcal C (M \otimes Y', X')

which amounts to saying that an optic (XX)(YY)\binom{X}{X'} \to \binom{Y}{Y'} is an equivalence class of triples

(M:𝒞,f:XMY,f:MYX) (M : \mathcal C, f : X \to M \otimes Y, f' : M \otimes Y' \to X')

I’m pretty sure everything in this post works for other categories of bidirectional processes such as mixed optics and dependent lenses, this is just a setting to write it down which is both convenient and not at all obvious.

The iteration functor is a functor Iter:𝐎𝐩𝐭𝐢𝐜(𝒞)𝐒𝐞𝐭\mathrm{Iter} : \mathbf{Optic} (\mathcal C) \to \mathbf{Set} defined on objects by

Iter(XX)=M:𝒞𝒞(I,MX)×𝒞(MX,MX) \mathrm{Iter} \binom{X}{X'} = \int_{M : \mathcal C} \mathcal C (I, M \otimes X) \times \mathcal C (M \otimes X', M \otimes X)

We refer to elements of Iter(XX)\mathrm{Iter} \binom{X}{X'} as iteration data for (XX)\binom{X}{X'}. We call the object MM the state space, the morphism x0:IMXx_0 : I \to M \otimes X the initial state and the morphism i:MXMXi : M \otimes X' \to M \otimes X the iterator.

Note that in the common case that 𝒞\mathcal C is cartesian monoidal, we can eliminate the coend to obtain a simpler characterisation:

Iter(XX)=𝒞(1,X)×𝒞(X,X) \mathrm{Iter} \binom{X}{X'} = \mathcal C (1, X) \times \mathcal C (X', X)

Given an optic f:(XX)(YY)f : \binom{X}{X'} \to \binom{Y}{Y'} given by f=(N,f:XNY,f:NYX)f = (N, f : X \to N \otimes Y, f' : N \otimes Y' \to X'), we get a function

Iter(f):Iter(XX)Iter(YY) \mathrm{Iter} (f) : \mathrm{Iter} \binom{X}{X'} \to \mathrm{Iter} \binom{Y}{Y'}

Namely, the state space is MNM \otimes N, the initial state is

Ix0MXMfMNY I \overset{x_0}\longrightarrow M \otimes X \xrightarrow{M \otimes f} M \otimes N \otimes Y

and the iterator is

MNYMfMXiMXMfMNY M \otimes N \otimes Y' \xrightarrow{M \otimes f'} M \otimes X' \overset{i}\longrightarrow M \otimes X \xrightarrow{M \otimes f} M \otimes N \otimes Y

This is evidently functorial. Funnily enough, although the action of Iter\mathrm{Iter} on objects when 𝒞\mathcal C is cartesian is easier to understand, its action on morphisms is less obvious and is not evidently functorial, instead demanding a small proof.

Pairing iterators and continuations

We have an existing functor K:𝐎𝐩𝐭𝐢𝐜(𝒞)op𝐒𝐞𝐭K : \mathbf{Optic} (\mathcal C)^{\mathrm{op}} \to \mathbf{Set}, given on objects by K(XX)=𝒞(X,X)K \binom{X}{X'} = \mathcal C (X, X'). This is the continuation functor, and it is the contravariant functor represented by the monoidal unit (II)\binom{I}{I}. (This functor first appeared in Morphisms of Open Games.)

For the remainder of this section I’ll specialise to the case 𝒞=𝐒𝐞𝐭\mathcal C = \mathbf{Set}, in which case an optic (XX)(YY)\binom{X}{X'} \to \binom{Y}{Y'} is determined by a pair of functions f:XYf : X \to Y and f:X×YXf' : X \times Y' \to X', and iteration data i:Iter(XX)i : \mathrm{Iter} \binom{X}{X'} is determined by an initial value x0:Xx_0 : X and a function i:XXi : X' \to X.

Given iteration data and a continuation that agree on their common boundary, we know enough to run the iteration and produce an infinite stream of values:

|:Iter(XX)×K(XX)Xω \left< - | - \right> : \mathrm{Iter} \binom{X}{X'} \times K \binom{X}{X'} \to X^\omega

Namely, this stream is defined corecursively by

x0,i|k=x0:i(k(x0)),i|k \left< x_0, i | k \right> = x_0 : \left< i (k (x_0)), i | k \right>

This operation is natural (technically, dinatural): for any iteration data i:Iter(XX)i : \mathrm{Iter} \binom{X}{X'}, optic f:(XX)(YY)f : \binom{X}{X'} \to \binom{Y}{Y'} and continuation k:K(YY)k : K \binom{Y}{Y'}, we have

i|K(f)(k)=fω(Iter(f)(i)|k) \left< i | K (f) (k) \right> = f^\omega \left( \left< \mathrm{Iter} (f) (i) | k \right> \right)

where fω():XωYωf^\omega (-) : X^\omega \to Y^\omega means applying the forwards pass of ff to every element of the stream. As a commuting diagram,

Here’s a tiny implementation of the iteration functor and the pairing operator in Haskell:

data Iterator s t = Iterator {
    initialState :: s,
    updateState :: t -> s
}

mapIterator :: Lens s t a b -> Iterator s t -> Iterator a b
mapIterator l (Iterator s f) = Iterator (s ^# l) (\b -> (f (s & l .~ b)) ^# l)

runIterator :: Iterator s t -> Lens s t () () -> [s]
runIterator (Iterator s f) l = s : runIterator (Iterator (f (s & l .~ ())) f ) l

The category of elements of Iterator

The next step is to form the category of elements Iter\int \mathrm{Iter}, also known as the discrete Grothendieck construction. This is a category whose objects are tuples ((XX),i)\left( \binom{X}{X'}, i \right) of an object (XX)\binom{X}{X'} of 𝐎𝐩𝐭𝐢𝐜(𝒞)\mathbf{Optic} (\mathcal C) and a choice of iteration data i:Iter(XX)i : \mathrm{Iter} \binom{X}{X'}. A morphism ((XX),i)((YY),j)\left( \binom{X}{X'}, i \right) \to \left( \binom{Y}{Y'}, j \right) is an optic f:(XX)(YY)f : \binom{X}{X'} \to \binom{Y}{Y'} with the property that Iter(f)(i)=j\mathrm{Iter} (f) (i) = j, that is to say, the iteration data on the left and right boundary have to agree.

The functor Iter:𝐎𝐩𝐭𝐢𝐜(𝒞)𝐒𝐞𝐭\mathrm{Iter} : \mathbf{Optic} (\mathcal C) \to \mathbf{Set} is lax monoidal: there is an evident natural way to combine pairs of iteration data into iteration data for pairs:

:Iter(XX)×Iter(YY)Iter(XYXY) \nabla : \mathrm{Iter} \binom{X}{X'} \times \mathrm{Iter} \binom{Y}{Y'} \to \mathrm{Iter} \binom{X \otimes Y}{X' \otimes Y'}

This means that the tensor product of 𝐎𝐩𝐭𝐢𝐜(𝒞)\mathbf{Optic} (\mathcal C) lifts to Iter\int \mathrm{Iter}, by

((XX),i)((YY),j)=((XYXY),ij) \left( \binom{X}{X'}, i \right) \otimes \left( \binom{Y}{Y'}, j \right) = \left( \binom{X \otimes Y}{X' \otimes Y'}, i \nabla j \right)

The category Iter\int \mathrm{Iter} can essentially already describe iteration with optics, although in a slightly awkward way. Suppose we draw a string diagram that not coincidentally resembles a control loop:

Here, ff and ff' denote some morphisms f:XYf : X \to Y and f:YXf' : Y \to X in our underlying category, and x0x_0 represents an initial state x0:IXx_0 : I \to X.

Normally string diagrams denote morphisms of a monoidal category, but we make a cut just to the right of the backwards-to-forwards turning point, and consider that everything left of that is describing a boundary object. Namely in this case, we have the object ((XX),i)\left( \binom{X}{X}, i \right) where the iteration data i:Iter(XX)i : \mathrm{Iter} \binom{X}{X} is given by the state space II, the initial state x0:IIXx_0 : I \to I \otimes X and the iterator id:IXIX\mathrm{id} : I \otimes X \to I \otimes X.

The remainder of the string diagram to the right of the cut denotes an ordinary optic f:(XX)(II)f : \binom{X}{X} \to \binom{I}{I}, namely the one given by f=(Y,f,f)f = (Y, f, f'), with forwards pass f:XYIf : X \to Y \otimes I and backwards pass f:YIXf' : Y \otimes I \to X. This boils down to describing the composite morphism f;f:XXf; f' : X \to X.

Overall, we can read this diagram as denoting a morphism ff in Iter\int \mathrm{Iter} of type f:((XX),i)((II),Iter(f)(i))f : \left( \binom{X}{X}, i \right) \to \left( \binom{I}{I}, \mathrm{Iter} (f) (i) \right). The iteration data on the right boundary is Iter(f)(i):Iter(II)\mathrm{Iter} (f) (i) : \mathrm{Iter} \binom{I}{I}, which concretely has state space YY, the initial state x0;f:IYx_0; f : I \to Y and iterator f;f:YYf'; f : Y \to Y.

This works in principle, but splitting the diagram between denoting an object and denoting a morphism is very non-standard. So far, this amounts to doing for the iteration functor what we did for the selection functions functor in section 6 of Towards Foundations of Categorical Cybernetics.

The full theory of iteration

Now we take the final step to fix the slight clunkiness of using Iter\int \mathrm{Iter} as a model of iteration. This continues the firmly established pattern that categorical cybernetics contains only two ideas that get combined in more and more intricate ways: optics and parametrisation.

There is a strong monoidal functor π:Iter𝐎𝐩𝐭𝐢𝐜(𝒞)\pi : \int \mathrm{Iter} \to \mathbf{Optic} (\mathcal C) that forgets the iteration data, namely the discrete fibration π((XX),i)=(XX)\pi \left( \binom{X}{X'}, i \right) = \binom{X}{X'}. This functor generates an action of the monoidal category Iter\int \mathrm{Iter} on 𝐎𝐩𝐭𝐢𝐜(𝒞)\mathbf{Optic} (\mathcal C), namely

((XX),i)(YY)=(XYXY) \left( \binom{X}{X'}, i \right) \bullet \binom{Y}{Y'} = \binom{X \otimes Y}{X' \otimes Y'}

See section 5.5 of Actegories for the Working Amthematician for far too much information about actegories of this form.

We now take the category 𝐏𝐚𝐫𝐚Iter(𝐎𝐩𝐭𝐢𝐜(𝒞))\mathbf{Para}_{\int \mathrm{Iter}} (\mathbf{Optic} (\mathcal C)) of parametrised morphisms generated by this action. We also refer to this kind of thing (parametrisation for the action generated by a discrete fibration) as the Para construction weighted by Iter\mathrm{Iter}, 𝐏𝐚𝐫𝐚Iter(𝐎𝐩𝐭𝐢𝐜(𝒞))\mathbf{Para}^\mathrm{Iter} (\mathbf{Optic} (\mathcal C)) - the name comes from it being a kind of weighted limit and I think the reference for this is Bruno’s PhD thesis, which is sadly unreleased as I’m writing this.

Working things through: an object of 𝐏𝐚𝐫𝐚Iter(𝐎𝐩𝐭𝐢𝐜(𝒞))\mathbf{Para}^\mathrm{Iter} (\mathbf{Optic} (\mathcal C)) is still a pair (XX)\binom{X}{X'}, but a morphism (XX)(YY)\binom{X}{X'} \to \binom{Y}{Y'} consists of three things: another pair of objects (ZZ)\binom{Z}{Z'}, iteration data i:Iter(ZZ)i : \mathrm{Iter} \binom{Z}{Z'}, and an optic (XZXZ)(YY)\binom{X \otimes Z}{X' \otimes Z'} \to \binom{Y}{Y'}.

Now suppose we have a diagram of an open control loop, that is to say, a control loop that is open-as-in-systems (not to be confused with an open loop controller, which is unrelated):

Here the primitive morphisms in the diagram are f:AXBYf : A \otimes X \to B \otimes Y, f:BYAXf' : B' \otimes Y \to A' \otimes X, and an initial state x0:IXx_0 : I \to X. The idea is that ff is the forwards pass, ff' is the backwards pass, and after the backwards pass comes another forwards pass but one time step in the future.

To make formal sense of this diagram, we imagine that we deform the backwards-to-forwards bend upwards, treating the state as a parameter, and then cut the diagram as we did before:

Now we can read this off as a morphism (XX)(YY)\binom{X}{X'} \to \binom{Y}{Y'} in 𝐏𝐚𝐫𝐚Iter(𝐎𝐩𝐭𝐢𝐜(𝒞))\mathbf{Para}^\mathrm{Iter} (\mathbf{Optic} (\mathcal C)). The (weighted) Para construction makes everything go smoothly, so this is an entirely standard string diagram with no funny stuff.

Technically categories of parametrised morphisms are always bicategories (or better, double categories), and I think this is a rare case where we actually want to quotient out all morphisms in the vertical direction, i.e. identify (f:(XZXZ)(YY),i:Iter(ZZ))\left( f : \binom{X \otimes Z}{X' \otimes Z'} \to \binom{Y}{Y'}, i : \mathrm{Iter} \binom{Z}{Z'} \right) with (g:(XWXW)(YY),j:Iter(WW))\left( g : \binom{X \otimes W}{X' \otimes W'} \to \binom{Y}{Y'}, j : \mathrm{Iter} \binom{W}{W'} \right) whenever there is any optic h:(ZZ)(WW)h : \binom{Z}{Z'} \to \binom{W}{W'} making Iter(h)(i)=j\mathrm{Iter} (h) (i) = j and commuting with ff and gg. Coming back to our earlier picture of cutting a string diagram, this exactly says that we identify all of the different ways we could make the cut. In order to do this we change the base of enrichment along the functor π0:𝐂𝐚𝐭𝐒𝐞𝐭\pi_0 : \mathbf{Cat} \to \mathbf{Set} taking each category to its set of connected components.

One final note: Almost everything in this post used nothing but the fact that Iter\mathrm{Iter} is a lax monoidal functor 𝐎𝐩𝐭𝐢𝐜(𝒞)𝐒𝐞𝐭\mathbf{Optic} (\mathcal C) \to \mathbf{Set}. With minimal translation, I think the entire thing works as a story about “forcing states in a symmetric monoidal category”: given any symmetric monoidal category 𝒞\mathcal C and a lax monoidal functor F:𝒞𝐒𝐞𝐭F : \mathcal C \to \mathbf{Set}, the category 𝐏𝐚𝐫𝐚F(𝒞)\mathbf{Para}^F (\mathcal C) is equivalently described as 𝒞\mathcal C freely extended with a morphism x:IXx : I \to X for every x:F(X)x : F (X). I’ll leave this as a conjecture for somebody else to prove.