definition theorem example program
A lens from a source type s to a focus type a is a pair
get :: s -> a
set :: s -> a -> sobjectifying read/write access to a part of a larger object (a field of a record, a component of a pair, a column of a database row — where lenses were first introduced). A lens is lawful if it satisfies
Sources: DaoFP §16.3 (“Lenses”), §17.9 (“Existential lens”), §18 (“Tambara Modules”, profunctor optics); §16.3 (“Comonad coalgebras”); Cruttwell et al. arXiv:2103.01931 (notes) Definition 2.4; Spivak arXiv:1908.02202 (notes) (generalized lenses).
Lenses are coalgebras of the Store Comonad. A coalgebra phi :: s -> Store a s, phi s = St (set s) (get s), satisfies iff set s (get s) = s, and iff phi . set s = \x -> St (set s) x, i.e. set (set s a) = set s (set/set) and get (set s a) = a (get/set). So lawful lenses are exactly the comonad coalgebras — the (co-)Eilenberg-Moore Category of the store comonad.
Other presentations: the existential lens (Coend, DaoFP §17.9), and profunctor optics — lenses as maps polymorphic over Tambara modules (type Lens s t a b = forall p. Tambara p => p a b -> p s t). All of these are special cases of optics.
The category of lenses
For a Cartesian Category , lenses assemble into a category (Cruttwell et al. arXiv:2103.01931 (notes), Definition 2.4):
- objects: pairs — think = values, = changes (or requests, or corrections) to values. When these are bimorphic lenses, the
s t a bof Haskell’s lens libraries; - morphisms : pairs with (the get, forward) and (the put, backward);
- identity: ;
- composition: forward , backward, pointwise,
Run the forward pass to get , push the incoming change back through at the point , then through at the point . That is the reverse-mode chain rule, and for the right it is backpropagation (Reverse Derivative Category); the fact that needs the forward input is why backprop caches activations. Formally, is the Grothendieck Construction of the fibrewise opposite of the simple fibration: the backward pass lives in the fibre over the point the forward pass visited.
is symmetric monoidal, , but not cartesian: a lens into the would-be terminal object needs a put , and there are many — so a backward wire cannot be silently deleted. Lenses into are exactly the costates that cap off a wire; in gradient-based learning the learning rate is such a cap (Gradient-Based Learning with Parametric Lenses).
| a lens is | used for | |
|---|---|---|
| , | a get/set pair (database views, records) | functional references |
| , | a map and its reverse derivative | backpropagation |
| , = utilities | a play map and a coplay map returning utilities | open games |
| a Markov Category, state-dependent | a channel and its Bayesian inversion | Bayesian lenses |
Adding a parameter wire gives parametric lenses, .
In compilers and databases
The view-update problem of databases is a lens problem: a query is get, translating view edits back is put, and the well-behavedness laws are GetPut and PutGet (Relational Lens).
Docs: plain Julia — Catlab has no dedicated API for this; related: Catlab v0.16 docs · GATlab standard library
# a lens on a NamedTuple field, and its laws checked on an example
struct Lens; get::Function; set::Function; end
fst_lens = Lens(p -> p.x, (p, a) -> (x = a, y = p.y))
p = (x = 1, y = 2)
fst_lens.set(p, fst_lens.get(p)) == p # set/get
fst_lens.get(fst_lens.set(p, 9)) == 9 # get/set
fst_lens.set(fst_lens.set(p, 5), 7) == fst_lens.set(p, 7) # set/setimport Mathlib
structure Lens (s a : Type) where
get : s → a
set : s → a → s
set_get : ∀ x, set x (get x) = x
get_set : ∀ x v, get (set x v) = v
set_set : ∀ x v w, set (set x v) w = set x w
def fstLens : Lens (α × β) α := ⟨Prod.fst, fun p a => (a, p.2), by simp, by simp, by simp⟩data Lens s a = Lens { get :: s -> a, set :: s -> a -> s }
fstL :: Lens (a, b) a
fstL = Lens fst (\(_, b) a -> (a, b))
-- the store-comonad coalgebra of a lens
phi :: Lens s a -> s -> Store a s
phi (Lens g st) s = St (st s) (g s)