Cookbook
Optics are probably the most powerful unused tool in our arsenal — this section is the working proof. Runnable patterns for the questions that come up most often, one page for each of the three jobs optics do best:
- Navigating structures — reach one
branch of a sum, every node of a recursive tree, several fields
at once, or a field inside bytes you never decode. One
.andThensurface for all of it. - Decoupling and modularity — replace a direct dependence on a data type with an optic: depend on the relationship, ask for the weakest capability you need, and let independent modules interoperate at the call site.
- Effect threading — turn control flow into type definitions: the optic pins where, the effect type decides what happens — abort, accumulate, audit, defer.
Two demos open the section: whole programs, with their own tests and benchmarks, that put these recipes to work.
- Allocation-free stream triage — an fs2 filter that classifies and filters a gRPC stream of 64-field JSON messages straight off a socket, reading the few values it needs with eo-jsoniter instead of decoding each message.
- Blockchain smart contract compilation — a compiler from Cardano's Plutus Core to JVM class files, built as a chain of lawful prisms and an iso, and checked against the reference evaluator on every program.
The framing follows Hansen — We Need More Optics (https://medium.com/swlh/we-need-more-optics-8ddf1d2d9468) and Penner — Optics By Example (https://leanpub.com/optics-by-example/). These are worked examples, not a tutorial — for the optic families themselves see the Optics reference. Every recipe cites its source.
If you arrive with a task rather than an optic in mind, start here:
| I want to… | Recipe |
|---|---|
| edit one branch of a sum type, misses passing through | Visit contingent fields |
| apply one edit at every node of a recursive tree | Visit across whole trees |
| update only the matching elements of a collection | Visit through arbitrary structure |
| fold several fields into one number | Isolate what you need |
| rewrite every slot of a fixed shape, or summarise a batch | Compute aggregations |
| update a grid coordinate or preserve device labels | Indexed: Grates and Glasses |
| change a field deep in JSON without decoding the payload | Edit JSON without decoding |
| depend on a relationship instead of a data type | Require the optic, not the type |
| ask for the weakest capability a function needs | Depend only on what's needed |
| edit Avro bytes on a hot path, no decode | Serdes free pipes |
| move one field between wire formats (Avro ↔ JSON) | Inspectable cross-format bridges |
| law-test the optics my module publishes | Re-usable laws for testing |
| make an in-place update fallible | Validate-in-place with modifyF |
| swap abort / accumulate / audit without touching the optic | Structure orthogonal to effects |
| batch N+1 lookups through one traversal | Batch-load nested IDs |
| stamp an effect's result back into the entity | Persist-and-stamp decode free |
Further reading
- Concepts — the theory behind the unified Optic
trait and carriers; the
composition lattice
diagram maps out every
Composer[F, G]bridge. - Optics reference — the full per-family tour, introduced by the family taxonomy diagram.
- Generics — macro-derived
lens[S](...)andprism[S, A]. - Circe integration — cursor-backed JSON optics; failure flow for the Ior decision tree.
- Avro integration — cursor-backed Avro optics; failure flow for the schema-driven Ior decision tree.
- Migrating from Monocle — side-by-side translation guide.
- Hansen — We Need More Optics, The Startup, https://medium.com/swlh/we-need-more-optics-8ddf1d2d9468 — the article this section's three pages follow.