Bay Area Haskell
Developers should adopt the full lens library in applications because compiler inlining erases nested record boilerplate at zero runtime cost, while public libraries should expose plain functions to avoid dependency bloat.
AI-assisted summary of John Wiegley: Putting Lenses to Work — original source. Check the original for full context.
Source published:
Key ideas
- Lenses operate cleanly over product types and computational projections, drastically simplifying updates in deeply nested data structures compared to standard Haskell boilerplate. (1:30)
- Prisms extend optics to sum types, returning Maybe values on previews and acting as safe no-ops during mismatched updates without requiring runtime error handling. (9:03)
- Combining lenses with MonadState via combinators like zoom, use, and .= delivers imperative-style state manipulation while preserving pure functional semantics. (24:03)
- Extensive use of INLINE and INLINABLE pragmas in the lens library allows GHC to boil off intermediate abstractions, resulting in negligible runtime overhead. (44:01)
Detailed notes
## Background This presentation was delivered by John Wiegley at BayHac 2017 and published on YouTube by the Bay Area Haskell channel on April 22, 2017. According to the page description and show notes, the talk demonstrates practical lens usage with accompanying slides hosted on GitHub.
## TL;DR John Wiegley demystifies Haskell's lens ecosystem by bypassing abstract category theory and focusing strictly on everyday productivity and practical ergonomics. Drawing from production experience at BAE Systems, he demonstrates how lenses, prisms, and traversals drastically simplify nested record updates and heterogeneous data manipulation compared to standard Haskell boilerplate. The talk explores powerful workflows like container indexing with at and ix, state manipulation using zoom and MonadState, and advanced manipulations via partsOf and biplate. Wiegley concludes with architectural guidance on GHC optimizations, managing dependencies across libraries versus applications, and writing zero-dependency lens functions.
## Atmosphere & style The presentation has an approachable, pragmatic, and unpretentious vibe that immediately strips away the intimidating reputation of Haskell optics. Wiegley speaks with conversational warmth and self-deprecating humor—readily pointing out flaws on his own slides and joking about cargo-coding from his personal scratchpads. It feels like an engaging, live workshop where complex abstractions are demystified through visceral code comparisons, leaving the audience feeling empowered rather than mathematically overwhelmed.
## Bulletin - [01:30](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=90s) Lenses operate cleanly over product types and computational projections, drastically simplifying updates in deeply nested data structures compared to standard Haskell boilerplate. - [09:03](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=543s) Prisms extend optics to sum types, returning Maybe values on previews and acting as safe no-ops during mismatched updates without requiring runtime error handling. - [24:03](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1443s) Combining lenses with MonadState via combinators like zoom, use, and .= delivers imperative-style state manipulation while preserving pure functional semantics. - [44:01](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2641s) Extensive use of INLINE and INLINABLE pragmas in the lens library allows GHC to boil off intermediate abstractions, resulting in negligible runtime overhead.
## Key takeaways - [01:30](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=90s) Lenses focus on subparts of product types that are guaranteed to exist, but they can also represent pure computational projections rather than strictly in-memory data structures. - [01:30](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=90s) Everyday lens programming requires only a small vocabulary of roughly ten operators, primarily centered on view (^.) and set (.~). - [04:00](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=240s) The primary practical advantage of lenses is updating deeply nested records; standard function composition (.) chains optics cleanly without manual record unpacking and repacking. - [10:04](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=604s) Prisms address sum types (coproducts) where a variant may or may not exist; previewing (^?) yields Maybe, while setting through an unmatched prism behaves as a safe no-op. - [16:20](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=980s) Traversals generalize access across zero, one, or many items; viewing a multi-target traversal with ^. requires a Monoid instance to combine values, whereas ^.. extracts them into a list. - [19:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1172s) Optics exist in a clear hierarchy: lenses provide read-write access to exactly one target, getters provide read-only access, and isos define lossless, bidirectional transformations. - [20:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1232s) Map manipulation serves as a primary motivator for lenses; at focuses on an entry as a Maybe value (enabling deletion via Nothing), while ix acts as a traversal that updates only existing keys. - [24:03](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1443s) The zoom combinator allows localized monadic state actions to focus strictly on a sub-state slice, enabling modular state design without exposing outer state structures. - [29:52](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1792s) Companion libraries like lens-aeson provide XPath-like navigation over untyped JSON structures, significantly easing data parsing in production codebases. - [31:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1892s) The partsOf combinator reifies all targets of a traversal into a single list for arbitrary bulk transformations (such as sorting or reversing) before transparently reinserting them. - [35:56](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2156s) The biplate traversal borrows from Scrap Your Boilerplate to traverse arbitrary nested heterogeneous structures, targeting all occurrences of a specified type across compiler ASTs. - [40:05](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2405s) Because van Laarhoven lenses are ordinary higher-order functions, library authors can expose lens-compatible interfaces with zero dependencies on the lens package itself. - [44:01](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2641s) GHC compiler optimization passes eliminate most lens overhead at compile time due to ubiquitous INLINE and INLINABLE pragmas across the library. - [45:10](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2710s) Library developers should minimize dependencies by writing raw lens functions or using microlens, while application developers should freely adopt the full lens package for its rich combinators.
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=0s) Practical Lenses Without the Theory
John Wiegley introduces his talk on lenses, setting clear expectations from the outset: the presentation assumes no prior experience or understanding of lenses. Crucially, there will be no theoretical deep-dives into lens types, and the vast majority of the intimidating lens operators will be intentionally ignored. Wiegley explains his background at BAE Systems, where his team conducts research and relies heavily on Haskell across numerous projects. In every single Haskell project they build, lenses play an essential role. He clarifies that this adoption is not driven by dogma: “in every single Haskell project we have there we use lenses and not because there's that one guy who thinks every project needs to have lenses in it but because all of us on the team have found they're extremely useful.”
Rather than cataloging every esoteric feature covered in academic blog posts, Wiegley focuses on the practical, everyday "killer apps" of lenses where the library vastly outperforms alternative Haskell approaches. His intended roadmap covers the essentials in progressive steps: - **Lenses and Prisms**: Getting foundational mechanics and intuition in place. - **Traversals**: The point where the real power of the lens abstraction kicks in. - **Maps and State**: The ultimate "killer combination," where lenses, prisms, containers, and stateful computations come together with extraordinary ergonomics. - **Optional advanced features**: Additional capabilities to be addressed only if time permits, noting that his team rarely touches many of them in production.
[01:30](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=90s) Product Types, Computations, and Basic Tuples
Wiegley defines a lens conceptually by distinguishing it from other optics: a lens targets a specific subpart of a structure that is guaranteed to always be present. In type-theoretic terms, lenses operate on product types—aggregations of multiple values where a designated position (such as the first element) is unconditionally available. Importantly, the word "structure" is placed in quotes because a lens does not have to focus strictly on an in-memory data layout like an array or concrete record. It can represent a purely computational projection. For example, given a raw timestamp represented as an integer count of seconds, the "hour" component is part of that computational structure. A custom lens can convert seconds into hours, minutes, and seconds, extract the hour, permit setting a different hour, and repackage the result back into raw seconds—handling all conversions and plumbing completely transparently behind the scenes.
The most primitive concrete lens operates on standard Haskell tuples. Product types expose positional lenses named `_1`, `_2`, `_3`, and so forth. To read through a lens, the primary infix operator is `^.` (spoken as "view"), which takes a data structure on the left and a lens on the right. For instance, given a 3-tuple, extracting the second element is written `(x, y, z) ^. _2`. The exact same operation can be invoked prefix-style using the `view` function: `view _2 (x, y, z)`.
```mermaid flowchart LR accTitle: Lens Access and Modification accDescr: Diagram showing how a lens either views a target from a product structure or updates the target using flipped function application. subgraph View ["Reading Data"] S1["Structure (x, y, z)"] -->|"^. _2 (view)"| T1["Target Element y"] end subgraph Set ["Writing Data"] S2["Structure (x, y, z)"] -->|"& _2.~ val (set)"| S3["New Structure (x, val, z)"] end ```
The functional form `view` is especially convenient in GHC view patterns, allowing developers to cleanly unpack and pattern-match on components of incoming arguments on the fly (for example, `(view _2 -> pat)`). To modify an element inside a product, the reverse operation is `set`, expressed as the operator `.~`. In practice, updates are almost always chained using the `&` operator (reverse/flipped function application, `Data.Function.(&)`), which places the target structure on the left side: `(x, y, z) & _2.~ "new"`. Wiegley notes that while lens syntax has an intimidating reputation, everyday usage relies on a very small vocabulary: “I only use like 10 operators on average so those are the ten you're going to see in this talk but view and set are the most used.”
[04:00](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=240s) Working with Records and Deeply Nested Updates
Records represent distinguished products where each field is labeled with a name rather than an index. Wiegley admits that simple, shallow records are probably the least interesting showcase for lenses: if a record only contains flat, primitive types, bringing in the lens library often feels like overkill compared to plain Haskell record update syntax. However, lenses completely transform the maintenance of deeply nested data architectures.
To configure a Haskell record for lenses, developers enable `TemplateHaskell`, import `Control.Lens`, prefix record field names with an underscore (e.g., `_field1`, `_field2`), and invoke the compile-time splice `makeLenses ''RecordName`. This automatically derives first-class lens identifiers without the leading underscores (`field1`, `field2`). Accessing and updating fields then mirror tuple operations precisely (`record ^. field1` and `record & field1.~ newValue`).
The true value of lenses appears when data structures become deeply nested:
```haskell -- Updating a nested field using lenses: record & innerRecord. targetField +~ 1
-- Equivalent pure standard Haskell record update: record { innerRecord = (innerRecord record) { targetField = targetField (innerRecord record) + 1 } } ```
In standard Haskell, modifying a field three levels down forces the programmer to explicitly pattern-match or unpack every intermediate layer, alter the target, and manually reconstruct every enclosing record to avoid discarding neighboring data. Lenses completely eliminate this tedious boilerplate. Because lenses compose with standard function composition (`.`), complex paths read naturally from outer to inner. Wiegley emphasizes the cognitive relief this compositionality brings: “once you start building up vocabularies of lenses to talk about your records talk about your different data types you can just start building arbitrarily complex chains and in your mind you can just think how am I traversing through this not how am I unwrapping and re-wrapping everything to preserve computability.”
[07:45](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=465s) The Common Lens Operators and Custom Lenses
Wiegley reassures the audience that writing lenses manually from scratch is rarely required in day-to-day code, advising them to treat his slide on the topic as a future reference template. Under the hood, a lens is a function accepting an applicative function `(a -> f b)` and an outer structure `s`, which traverses to the target, applies the effectful arrow, and sequences the outer `f t` result back up.
For real-world work, developers only need a compact toolkit of recurring operators: - `^.` (`view`): Reads the value focused by a lens. - `.~` (`set`): Replaces the focused value. - `?~`: Replaces a target field expecting a `Maybe` value with `Just x` (e.g., `record & optionalField?~ 10`). - Mathematical update operators: `+~`, `-~`, `*~`, `**~`, which modify numerical values in place. - `<>~` ("monoid tilde"): Appends a value to a focused target using `mappend` or `<>`, exceptionally handy for appending items directly to lists stored inside deeply nested records. - `%~` ("percent tilde"): Applies an arbitrary pure function to the target (`record & field %~ toUpper`). - `%%~` ("percent percent tilde"): Applies an arbitrary effectful/applicative function to the target, bubbling the resulting effects to the outside.
[09:03](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=543s) Introducing Prisms for Sum Types
Transitioning from lenses to prisms, Wiegley contrasts their theoretical roles: while lenses focus on parts of product types that are guaranteed to exist, prisms address sum types (coproducts) where a particular variant may or may not be present. If an anonymous pair `(a, b)` belongs to the domain of lenses (`_1`, `_2`), an `Either a b` belongs to the domain of prisms, focusing on `_Left` or `_Right`. Prisms give developers a uniform, composable tool to inspect, match, and modify data that might fail to match a specific constructor.
To use prisms on custom algebraic data types (ADTs), developers declare normal Haskell sum types without any special naming tricks and call `makePrisms ''DataType` via Template Haskell. By convention, generated prisms retain a leading underscore (such as `_Alpha`, `_Beta`, `_Gamma`), contrasting with `makeLenses` which strips leading underscores. If a constructor contains fields, the prism provides access to those enclosed values if and only if the data matches that specific constructor.
[10:04](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=604s) Prisms, Optional Targets, and the Power-to-Effort Ratio
When inspecting sum types via prisms, viewing a target uses the `^?` operator (`preview`) rather than `^.` (`view`), because the target value may simply not be present in that constructor. If the value matches the targeted constructor (such as extracting the second field of an `Alpha` constructor), the operation yields `Just` that value; if it encounters a different constructor like `Gamma`, it safely evaluates to `Nothing`. Setting a value through a prism behaves just as gracefully: setting the field when the constructor matches updates it as expected, but targeting a non-matching constructor simply results in a no-op that returns the original data unchanged. As John Wiegley observes, this allows developers to write generic algorithms over heterogeneous data structures without sprinkling pattern-matching boilerplate throughout their logic: “I can write it in terms of lenses and not care and then the user who gives it to me I'll do the right thing if it's the value I want otherwise [it's a no-op].”
```mermaid flowchart LR accTitle: Prism access and modification behavior accDescr: Diagram showing how prism viewing and setting branches depending on constructor match.
subgraph Input["Input Data"] V["Data Value"] end
subgraph Check["Constructor Check"] M{"Matches Prism?"} end
subgraph ReadOps["Viewing (^?)"] R_Yes["Just field_value"] R_No["Nothing"] end
subgraph WriteOps["Setting (.~)"] W_Yes["Updated Data Structure"] W_No["Original Data Structure (No-op)"] end
V --> M M -->|"Yes"| R_Yes M -->|"No"| R_No M -->|"Yes"| W_Yes M -->|"No"| W_No ```
When an audience member asks about deliberately throwing an error if the targeted constructor is missing, Wiegley encourages them to experiment during the hackathon. He then highlights the dramatic difference in code complexity between using lenses and writing manual pattern matching. Incrementing a nested field without lenses requires explicitly casing on the top-level constructor, unpacking the inner record, updating the specific field, and repackaging the whole structure. Combining prisms and lenses into deep compositions makes this disparity stark: in one real-world codebase involving nested records and maps, Wiegley wrote the pure manual Haskell code to convince a skeptical coworker, and “it was literally 20 lines of Haskell code doing nothing except with the one line of lens code is doing.” The real payoff of the lens library scales directly with structural nesting and depth.
- The prism preview operator `^?` returns `Maybe a`, resolving to `Just x` on a match and `Nothing` on a mismatch. - Setting through an unmatched prism behaves as an identity operation (a no-op) rather than throwing a runtime error. - Lens compositions accumulate type class constraints; mismatched optic operators result in dense, opaque compiler error messages about missing instances. - Manually writing prisms involves defining two functions: an injection into the sum type and a breakdown function returning an `Either`.
[13:26](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=806s) Traversals and Indexed Access
Moving beyond prisms—which target zero or one item—Wiegley introduces traversals, which reference zero, one, or many items across a structure, such as every item in a list. While prisms and traversals share conceptual overlap, traversals generalize access to multiple targets. A prime example is the indexed optic `ix`, which can focus on a specific position like `ix 1` (the zero-indexed second element of a list). Viewing via `^?` with `ix 1` yields `Just 2` on `[1, 2, 3]`, while on a list that is too short, it harmlessly yields `Nothing`. Setting through `ix` follows the same semantics: setting index 1 updates the element if present, or leaves a shorter list completely untouched.
When asked why indexed lookup is modeled as a traversal rather than a prism, Wiegley remarks that Edward Kmett—whom he characterizes as “sort of like the Yoda of lens”—would know the foundational categorical reasons. Wiegley then shows a custom traversal he wrote called `digits`, which explodes an integer into its individual decimal digits so optics can target individual positions, such as asking for the third digit of $\pi$ (returning `Just 4`). Demonstrating how optics can express arbitrary computation, setting the third digit to `99` expands the integer value accordingly. While admitting that such custom optics can stretch lawful behavior—recalling an attempt to build a Python-like slice optic that worked mechanically but failed optic laws—they demonstrate the sheer flexibility of optics as computational accessors.
- A traversal references zero or more targets across a data structure. - `ix n` focuses on the $n$-th element of an indexed collection, behaving like an optional single-element traversal. - Custom traversals can treat computations (like converting an integer into a list of digits via `show` and `read`) as lenses/traversals into internal structure.
[16:20](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=980s) Monoids, Aggregations, and Folds
A frequent source of beginner confusion occurs when applying the standard view operator `^.` to a traversal: GHC emits an error complaining about a missing `Monoid` instance. Wiegley explains that this happens because viewing a traversal by default folds all focused targets together using their `Monoid` append operation (`<>`). To collect all targeted items into a plain list rather than mashing them into an underlying monoid, lens provides the `^..` operator (often pronounced "to list of"), which lifts each visited element into a singleton list before folding.
```mermaid flowchart TD accTitle: Viewing Traversals via Monoids accDescr: Shows the distinction between viewing a traversal with caret-dot versus caret-dot-dot.
T["Traversal Targets: [x1, x2, x3]"]
subgraph Dot["Operator: ^."] M_App["Requires Monoid a<br/>x1 <> x2 <> x3"] M_Res["Aggregated Monoid Value"] end
subgraph DotDot["Operator: ^.."] L_Wrap["Wrap to Singletons<br/>[x1] <> [x2] <> [x3]"] L_Res["List of Targets: [x1, x2, x3]"] end
T --> Dot M_App --> M_Res T --> DotDot L_Wrap --> L_Res ```
Wiegley illustrates this monoidal behavior by combining `traverse` with the `to` combinator, which lifts any ordinary Haskell function into a getter. Applying `to Sum` across a list and viewing with `^.` combines all elements into a monoidal `Sum` wrapper. In contrast, using `^..` simply pulls the targets out into a list. Beyond raw viewing, the lens ecosystem provides a vast vocabulary of folds—read-only traversals that consume elements without providing an update path. Functions like `allOf` allow queries such as checking if the second element of every tuple across a collection is even. While equivalent logic can be written with vanilla higher-order Haskell functions, folds allow developers to reuse their existing lens pipelines directly inside analytical queries without rewriting data extraction routines.
- Viewing a multi-target traversal with `^.` demands a `Monoid` instance to combine the extracted values into a single result. - The `^..` operator (`toListOf`) extracts all traversal targets into a Haskell list `[a]`. - The `to` combinator lifts an arbitrary pure function `(a -> b)` into a read-only getter. - Folds represent read-only traversals, making up one of the largest collections of combinators in the lens package (e.g., `allOf`, `anyOf`).
[19:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1172s) Optic Hierarchy Vocabulary: Lenses, Getters, and Isos
To help navigate the dense type signatures in the `lens` Haddock documentation, Wiegley summarizes the foundational optic hierarchy. A *lens* provides read-write access to exactly one target. A *getter* weakens this contract by being strictly read-only on a single target, which relaxes the required type class constraints and allows any pure function to be lifted into one using `to`. An *iso* (isomorphism) models a lossless, bidirectional transformation between two distinct types that represent the identical underlying information, permitting navigation forward and backward without losing data.
An audience member asks for intuition regarding isos, and Wiegley explains them as direct isomorphisms between equivalent representations. A classic example in the lens library is the `lazy` iso, which translates between strict and lazy variants of types such as `ByteString` or `Text`. By inserting `. lazy.` directly into the middle of a lens composition chain, a developer can seamlessly transform a strict byte string into its lazy representation mid-traversal, operate on it with functions expecting lazy data, and let the optic pipeline handle bidirectional conversion automatically.
- **Lens**: Read-write access targeting exactly one value. - **Getter**: Read-only access targeting exactly one value; can be created from any function via `to`. - **Iso**: A bidirectional, lossless isomorphism representing an alternate view of identical data (e.g., strict and lazy `ByteString` conversions via `lazy`). - **Traversal**: Read-write access targeting zero, one, or many values. - **Fold**: Read-only access targeting zero, one, or many values.
[20:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1232s) Traversals, Prisms, and Map Access with `at` and `ix`
Reviewing optics across cardinalities, John Wiegley notes that while a lens focuses on exactly one target, a prism targets zero or one element, and traversals target zero or more elements. When querying targets that might not exist, operators like `^?` (preview) are the most common tool. There is also `^?!`, which acts as an assertive lookup: “question mark bang says I know it's there give me an error if it's not.” While potentially dangerous, he acknowledges there are practical situations where developer reasoning guarantees an element's presence.
```mermaid flowchart TD accTitle: Map Access Comparison: at vs ix accDescr: Visual flow comparing the semantics of the at lens and the ix traversal when setting values in a map.
subgraph AT["at Key (Lens)"] A1["Set Just v"] --> A2["Inserts key or overwrites existing value"] A3["Set Nothing"] --> A4["Deletes key if present"] end
subgraph IX["ix Key (Traversal)"] B1["Key Present"] --> B2["Updates value in place"] B3["Key Absent"] --> B4["No-op (does nothing, no insertion)"] end ```
Wiegley credits Haskell's map types as the primary motivation that brought him to the lens ecosystem in the first place: “Maps are what really pushed me into lens because the things you can do with maps means you don't have to remember all the functions.” Rather than juggling specific functions from `Data.Map`, lenses provide a unified interface. He explains the distinction between `at` and `ix`: - `at` is an accessor lens focusing on an element as a `Maybe`. Running `^. at 1` produces `Just v` if present or `Nothing` if absent. Assigning via `at 1.~ Just 'c'` inserts the key if missing, whereas assigning `.~ Nothing` deletes the entry altogether. - Catching an error on his own slide where a lookup should have returned `Nothing`, Wiegley jokes with the audience: “Use the lens on this presentation in which everything is correct.” - `non` pairs naturally with `at` to provide default fallbacks when wrapping or unwrapping `Maybe` values. Writing `at 2. non 'z'` returns the value if present, or replaces `Nothing` with `'z'` if missing, allowing you to supply defaults without resorting to full prisms. - `ix` acts as a traversal rather than an explicit `Maybe` lens. Demanding with `ix` via `^?!` is unsafe if the key is missing. Furthermore, setting with `ix` (`ix 1.~ 'c'`) will only update the value if the key already exists; if the key is not in the map, `ix` simply does nothing. - `failing` serves as an alternative combinator for traversals, running a secondary traversal if the primary one matches zero targets.
[24:03](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1443s) Operating on State: `use`, Assignments, and Scoping with `zoom`
The synergy between optics and `MonadState` provides another substantial productivity boost, bridging Haskell's purity with the intuitive feel of imperative programming. When state contains records or nested tuples, the `lens` library provides analogs to common state primitives: `use` functions like `get` focused through a lens, `uses` fetches the focused target and applies an egress transformation function, `preuse` safely accesses targets through a prism or traversal, and `preuses` pairs that lookup with a function. Mutating state within a monad becomes strikingly clean with operators such as `.=` (set) and `%=` (modify):
> “And this is kind of cool because now you can modify subsections of your state writing code that begins to look a lot like C.”
For instance, targeting a four-element list in state with `ix 1.= 5` will safely update the second element if the list has sufficient length, or silently do nothing without throwing an error if it does not. Monadic actions can also be executed directly on targets inside state, such as applying a monadic setting operator (`<<?=` or `assign`) or using `%=` to apply functions. Because the identity function `id` represents a trivial isomorphism (every type is isomorphic to itself), one can even write `id %= f` over a raw primitive state value.
```mermaid flowchart LR accTitle: Zooming into Monadic Sub-State accDescr: Zoom lifts a monadic action operating on a focused sub-state into a larger compound state context.
subgraph Outer["Outer State: ((Int, Int), Int)"] S1["_1:: Lens' Outer (Int, Int)"] end
subgraph Inner["Sub-State: (Int, Int)"] Action["stateAction:: MonadState (Int, Int) m => m ()\n_2.= 4"] end
Outer -->|"zoom _1"| Inner ```
The real architectural standout in state manipulation is `zoom`. While composing lenses inline allows deep reads and writes, `zoom` executes an entire monadic sub-action whose localized state context is restricted solely to the slice focused by the lens:
> “Zoom allows you to program differently each function can specifically address only the kind of state it is interested in and you can keep lifting those building blocks into larger composed State actions using Zoom.”
By writing small, directed functions that know only about their immediate sub-state, developers avoid polluting domain logic with global application state records. A function designed to operate on a simple pair can be embedded effortlessly via `zoom` into a triple, a quadruple, or an arbitrary outer application environment.
- `&~` offers an alternative to chaining consecutive `& field.~ val` updates on records. By writing `record &~ do`, the right-hand side becomes a `MonadState` block where fields can be populated sequentially using `.=` and other stateful operators before producing the final modified value.
[29:52](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1792s) The Broader Lens Ecosystem: `lens-aeson`, Indexed Optics, and Zippers
Wiegley outlines the wider lens ecosystem and specialized companion packages that extend the core concepts into diverse domains. While he admits he does not personally utilize every single package, several solve acute industrial problems. Chief among them is `lens-aeson`, which allows developers to traverse unstructured, arbitrary JSON documents without writing boilerplate serialization types:
> “That right there lens-aeson makes it very easy to sell lens inside my company to people who haven't worked on their projects before.”
He compares navigating JSON structures with `lens-aeson` to querying documents with XPath. Beyond JSON, the ecosystem spans a broad variety of specialized tools: - **Writer and Action Optics**: Libraries like `lens-action` accommodate optics producing monadic effects, alongside writer-oriented optics mirroring `zoom`. - **`thyme`**: A comprehensive time library that supplies lenses directly into calendar dates, timezones, and hours. - **Indexed Lenses**: Optics that supply the current index or key alongside each element encountered during traversal. - **Zippers**: Optics-based zippers allow deep navigation down into a hierarchical data structure to a specific target, followed by localized lateral movements across sibling nodes without needing to re-traverse the entire object graph from the root. - **System and Collection Lenses**: Dedicated optic packages for handling exceptions, primitive arrays, vectors, and filepaths.
[31:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1892s) Reifying Traversals with `partsOf`
John Wiegley transitions from numerical conversions—such as switching between bases like hex and octal or doing arithmetic operations via lenses—to introduce a feature meant to showcase the full, mind-bending power of the lens ecosystem: “The last sort of thing I want to show you here is a very, very cool thing that will blow your mind, and if you want to keep your mind just stay away from these slides.” While standard traversals allow you to inspect or update multiple target elements across structures such as maps or lists, `partsOf` completely changes how you interact with those targets.
`partsOf` takes any traversal and presents all targeted elements collectively as a single, reified list. This abstraction allows you to apply arbitrary list transformations—such as sorting, filtering, or reversing—to that consolidated structure before `partsOf` transparently weaves the modified values back into their original locations in the parent container. In Wiegley's first example, he uses `traversed` (specifically with the `d` suffix to access positional indices) paired with `indices odd` to select elements at positions 1, 3, 5, etc., and reverse only those elements while leaving the rest of the list untouched.
```mermaid flowchart LR accTitle: partsOf Transformation Flow accDescr: Diagram illustrating how partsOf extracts traversed targets into a list, applies a transformation, and writes them back.
subgraph Input ["Original Structure"] A["List: e.g. [a, b, c, d, e]"] end
subgraph Extraction ["Lens Extraction"] B["Traversal (`traversed. indices odd`)"] C["`partsOf` turns targets into List: [b, d]"] end
subgraph Manipulation ["List Modification"] D["Apply list function: `reverse` -> [d, b]"] end
subgraph Reinsertion ["Write-back"] E["Reassembled List: [a, d, c, b, e]"] end
A --> B B --> C C --> D D --> E ```
- When using `partsOf` to write back another list of values, if the replacement list is too short, `partsOf` only overwrites up to the available elements, leaving remaining slots untouched. - If the replacement list contains more items than the traversal selected, `partsOf` silently ignores the excess values rather than raising runtime errors. - The slides and full code are publicly available on GitHub at `jwiegley/putting-lenses-to-work`, featuring an accompanying `lenses.hs` scratchpad that Wiegley admits he uses himself: “Which is actually what I use to cargo-code my own Haskell. I often forget how to use something.”
---
[33:31](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2011s) Practical and Declarative Transformations with `partsOf`
Wiegley walks through several examples demonstrating how `partsOf` replaces what would otherwise be convoluted, imperative Haskell loops with concise, declarative lens expressions. In one example, he targets every element in a list that is strictly less than 4, sorts those elements in place, and reverses them. Wiegley highlights the expressive contrast: “Could you imagine trying to write this in plain Haskell? Whereas lens lets me state it in a declarative way exactly what I mean: all the parts of this list that are less than four, I want to sort them in place and reverse them, please.”
He extends this concept across other types of data structures: - **Tuples with `each`:** Combining `partsOf` with the `each` traversal treats a gigantic tuple as if it were a homogenous list, enabling direct sorting of tuple components in place without writing bespoke tuple shufflers. - **String overwriting:** Traversing all alphabetic characters in a target string like `"Hello, World!"` and writing the string `"Howdy"` over it. `partsOf` replaces only the traversed letters one-for-one and leaves the comma, space, and exclamation mark intact. - **Template masking:** Overwriting specific segments of a formatted time string (e.g., `00:00:00`). By traversing characters at or beyond index 3 that match `'0'`, new numerical strings can be spliced into the exact minutes and seconds positions without disturbing formatting punctuation.
---
[35:56](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2156s) Deep Generic Traversals with `biplate`
Venturing deeper into advanced traversal territory, Wiegley introduces `biplate`, an extraordinarily potent traversal borrowed from Scrap Your Boilerplate (SYB) and Uniplate concepts: “You can't not know about `biplate`, and you don't want to know how `biplate` is implemented either.” `biplate` traverses arbitrary, deeply nested, heterogeneous data structures (such as nested tuples containing multiple disparate types) and surfaces every element matching a specific target type.
An audience member asks about the type class constraints powering this mechanism, to which Wiegley responds: “Does it require a `Typeable` constraint? Yeah, but now you get `Typeable` on everything post-GHC 7.8.” Because `biplate` extracts targets based on their types, type annotations frequently become part of the lens expression itself so the compiler can infer which constructor payload or sub-element to target.
Wiegley demonstrates chained combinations of `biplate` and `partsOf`: - Extracting every `String` scattered across a nested tuple, using `partsOf` to treat them as a list, and reversing their order across the nested data structure. - Targeting all `Char` values that are lexically less than `'m'`, feeding them into `partsOf`, and sorting them so characters before `'m'` reorder while all characters above `'m'` stay in place. - Targeting all strings in a complex structure, picking their head characters, and capitalizing them using `toUpper`.
---
[40:05](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2405s) Zero-Dependency Lenses and AST Traversal Applications
Concluding the slide walkthrough, Wiegley reassures the audience that despite lens's intimidating reputation for wild operators and abstract category theory, you do not need to master the math to be effective. He points newcomers to the `#haskell-lens` IRC channel for help from experts. He highlights an essential architectural property: you can expose lens-compatible fields in your own libraries without taking on a heavy dependency on Edward Kmett's `lens` package: “Since lenses are just functions, you can put them in your library with zero dependency on lens and they will just work, because all these fancy lens types when you look under the hood, they're just functions with constraints like `Applicative`, `Functor`, `Traversable`, etc.”
An audience member raises an example of using deep traversal to rewrite qualified variable references across compiler abstract syntax trees (ASTs). Wiegley connects this to his earlier remarks on compiler architecture and SYB:
> “So if you write a compiler, you've got gigantic ASTs, and a lot of times there will be common little bits that are all throughout your AST but in different positions, and you don't really care about the structure of the thing you just parsed. You just want to say, sweep through my AST and do a variable substitution, or sweep through my AST and do this operation on all fields for every constructor that have this type in common. In the old days, you would use Scrap Your Boilerplate to very easily write a function like that... `biplate` is that within the lens world.”
Finally, Wiegley revisits the skipped theoretical slide on composition. Because lenses are ordinary functions, lens composition is simply standard Haskell function composition (`(.)`), making lenses pluggable into view patterns, pattern guards, and pattern synonyms throughout application code with minimal runtime overhead.
[44:01](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2641s) Compiler Optimizations and Inlining in the Lens Library
Addressing questions regarding runtime overhead and performance, John Wiegley explains that lens operations benefit from the exact same optimization passes that standard Haskell functions receive in GHC. While some functional abstractions risk incurring heavy performance penalties, nearly all combinators and helpers across Edward Kmett’s `lens` library are explicitly annotated with `INLINE` or `INLINABLE` pragmas. As a result, the intermediate structures and high-level combinator chains typically evaporate during compilation. As Wiegley puts it, “nearly everything in the lens library is marked inline or in lineable so almost all of this syntax that you're seeing here just gets boiled off.”
In practical day-to-day code, Wiegley notes that switching to lenses rarely produces a noticeable performance regression, though exact characteristics will always depend on the specific workloads and data structures involved. For developers eager to understand the mathematical mechanics and conceptual elegance underlying these abstractions, he points toward Simon Peyton Jones’s widely praised lecture on Skills Matter, which walks through lenses from first principles. Reflecting on his own learning journey, Wiegley remarks, “I think it's very clear and extremely well presented,” crediting that particular presentation as the breakthrough that finally allowed the concepts to click for him.
- **GHC Optimization:** Combinators annotated with `INLINE` and `INLINABLE` allow the compiler to strip away syntax wrappers and generate efficient machine code. - **Recommended Resource:** Simon Peyton Jones's Skills Matter presentation on lenses is recommended as the definitive conceptual primer for understanding the underlying theory.
[45:10](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2710s) Lens Representations, Dependencies, and Library vs. Application Design
When the discussion turns toward theoretical representations, Wiegley clarifies that the talk focuses on standard van Laarhoven lenses rather than profunctor optics or naive pairs of getter and setter functions. An audience member raises the perennial concern of the `lens` package's notorious footprint and dependency graph. Wiegley acknowledges the ecosystem of lightweight alternatives available on Hackage—such as `microlens`, `picolens`, and `lens-family-core`—which offer subsets of functionality with minimal dependencies. He also highlights that the core `lens` library has actively slimmed down over time by shedding packages like `split` and spinning out specialized domains into separate companion packages such as `lens-action` and `lens-aeson`.
```mermaid flowchart TD accTitle: Lens Dependency Decision Flowchart accDescr: Decision chart comparing whether to depend on full lens or vanilla functions based on project type.
Start["New Haskell Project"] --> Decision{"Are you writing an application or a public library?"}
subgraph AppTrack ["Application Development"] Decision -->|"Application"| AppChoice["Depend directly on 'lens'"] AppChoice --> AppBenefit["Get full operator access, prism helpers, and state combinators without friction"] end
subgraph LibTrack ["General-Purpose Library"] Decision -->|"Library"| LibChoice["Avoid heavy dependencies"] LibChoice --> LibSolution["Define lenses as plain van Laarhoven functions or use 'microlens'"] end ```
Wiegley draws a sharp pragmatic distinction between authoring standalone libraries for Hackage versus building internal production software. If you are developing a library intended for broad adoption across the Haskell community, pulling in a large dependency graph is discouraged; instead, authors should understand the van Laarhoven type signature and write vanilla lens functions directly without depending on the `lens` package at all. Conversely, when developing an application, developer velocity and access to rich combinators outweigh dependency count concerns. Wiegley summarizes the strategy directly: “if you're writing a library intended for consumption by the whole Haskell world, learn the theory and write your lenses as functions; if you're writing an application who cares, just put in lens as your dependency.” When asked why the library is still needed if everything compiles away, he points out that GHC still requires the definitions and types at compile time. Following a brief final exchange on reader associations, Wiegley thanks the BayHac audience and concludes the presentation.
- **Lightweight Lens Ecosystem:** - `microlens`: A modular, lightweight subset designed for minimal build overhead. - `picolens`: A tiny alternative offering fundamental lens functionality. - `lens-family-core`: A dependency-free approach to van Laarhoven lenses. - **Decoupled Packages:** Historical extra dependencies once bundled into `lens` (such as `lens-aeson`, `lens-action`, and utilities from `split`) have been factored into standalone libraries to keep the core package leaner.
## Notable quotes > [45:10](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2710s) if you're writing a library intended for consumption by the whole Haskell world, learn the theory and write your lenses as functions; if you're writing an application who cares, just put in lens as your dependency. — John Wiegley
> [40:05](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2405s) Since lenses are just functions, you can put them in your library with zero dependency on lens and they will just work, because all these fancy lens types when you look under the hood, they're just functions with constraints like `Applicative`, `Functor`, `Traversable`, etc. — John Wiegley
> [20:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1232s) Maps are what really pushed me into lens because the things you can do with maps means you don't have to remember all the functions. — John Wiegley
## Mentioned John Wiegley, Edward Kmett, Simon Peyton Jones, Bay Area Haskell, BAE Systems, BayHac, GHC, Skills Matter, GitHub, Hackage, Template Haskell, Control.Lens, lens-aeson, lens-action, thyme, microlens, picolens, lens-family-core, split, Scrap Your Boilerplate, Uniplate, Python, XPath
## Video description Presented at BayHac 2017 https://wiki.haskell.org/BayHac2017
Slides: https://github.com/jwiegley/putting-lenses-to-work/blob/master/putting-lenses-to-work.pdf
[Source](https://www.youtube.com/watch?v=QZy4Yml3LTY)