Lenses and Optics — Brief ☧
Deep version → | Back to Category Theory →
Q: Imagine you have a filing cabinet. Inside one drawer is a folder
for a family, inside that folder is a page about a specific person, and
on that page is their name. You want to change just the name without
pulling out the entire cabinet. Sound familiar?
A: Completely. In programming, we face this all the time with nested
data. Think about a dictionary inside a list inside another dictionary.
To reach a deeply nested field, you normally have to dig through each
layer, make your change, and then reassemble everything. A lens is
a tool that lets you "focus" on a specific part of a nested structure.
It gives you two abilities: get the value at that focus point, and
set a new value there — without touching anything else.
# For God so loved the world that he gave his only begotten Son, # that whoever believes in him should not perish but have eternal life. # John 3:16 tribe_chirho = {"name_chirho": "Judah", "leader_chirho": {"name_chirho": "Nahshon"}} # A lens focusing on the leader's name: get_leader_name_chirho = lambda t_chirho: t_chirho["leader_chirho"]["name_chirho"] # Returns: "Nahshon"Q: That makes sense for two levels deep. But what if we need to go
three or four levels? A nation contains a tribe, which contains a family,
which contains a person. Do we have to write one giant accessor each time?
A: Not at all — this is where it gets elegant. Lenses compose,
just like functions compose. If you have one lens that focuses from a
nation down to a tribe, and another that focuses from a tribe down to
its leader, you can snap them together like puzzle pieces. The combined
lens focuses straight from the nation to the leader. This is really just
function composition under the hood —
chain together simple steps to build powerful accessors.
Q: So it is like building a zoom lens out of smaller lens elements?
A: Exactly that analogy. And beyond just "get" and "set," there is a
third operation called
over— it lets you modify the focused value byapplying a function to it. Get, set, and modify: three operations that
handle almost every nested data access pattern you will encounter.
Why Lenses Matter for This Project
| Concept | Lens Analogy | Our System |
|---|---|---|
| Focus into structure | Get/Set | Read/Write a field in HBM domain |
| Compose accessors | Lens composition | Navigate hierarchical domains (L0 → L1 → L2) |
| Transform in place | over (modify) | Update a single bit in a 262K domain |
| Profunctor optics | Generalized lenses | Edward Kmett's lens library — the gold standard |
The table above shows how lens concepts map directly onto our FPGA domain hierarchy. Each row represents the same fundamental idea -- accessing nested structure -- expressed in the language of optics and then in the concrete terms of our system. The key takeaway is that lenses are not just an abstract functional programming concept; they describe a pattern that appears everywhere you have hierarchical data, including custom hardware.
Edward Kmett's lens library is one of the most influential Haskell libraries.
It uses profunctor optics — lenses generalized through category theory — to compose
arbitrary accessors. Our FPGA domain hierarchy is, in essence, a three-level lens
into an array-like structure:
Hierarchical256kChirho: L0 summary → L1 group → L2 data word → individual bit
Each level acts like a lens focusing one step deeper into a tree-shaped
data layout, and composition chains them into a single path from root to leaf. When the FPGA needs to check whether a specific value (say, value 42,000) is still in a domain of 262,144 possible values, it performs exactly the same three-step zoom that a composed lens performs: first check the Level 0 summary bit to see if that region is alive, then check the Level 1 group word, then read the specific bit in the Level 2 data word. If any level says "nothing here," the lens short-circuits and the FPGA skips the deeper levels entirely. This is a hardware lens operating in a single clock cycle.
Soli Deo Gloria