Wiegley Cuts Haskell Lens Suite to 10 Operators for Production Apps

John Wiegley: Putting Lenses to Work — Bay Area Haskell

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)

中文简报

Wiegley将生产级应用的Haskell Lens精简至10个核心操作符 开发者应在应用程序中引入完整的lens库,因为编译器内联能在零运行时成本下消除嵌套记录样板代码,而公共库则应暴露普通函数以避免依赖膨胀。 John Wiegley避开抽象的范畴论,完全立足于日常生产力与实用体验,对Haskell的lens生态系统进行了清晰剖析。结合在BAE Systems的生产实践经验,他展示了相比标准Haskell样板代码,lens、prism和traversal如何大幅简化深层嵌套记录的更新与异构数据处理。该演讲探讨了使用at和ix进行容器索引、借助zoom和MonadState进行状态操作,以及通过partsOf和biplate执行高级操作等高效工作流。Wiegley最后就GHC优化、库与应用间的依赖管理,以及编写零依赖lens函数给出了架构层面的指导建议。 Lens可纯粹且规范地作用于积类型与计算投影,相较于标准Haskell样板代码,极大简化了深层嵌套数据结构的更新。 (1:30) Prism将光学概念扩展至和类型,在预览时返回Maybe值,并在不匹配的更新中安全地保持无操作,无需运行时错误处理。 (9:03) 通过zoom、use和.=等组合子将lens与MonadState结合,可在保留纯函数式语义的同时,提供命令式风格的状态操作。 (24:03) lens库中大量使用的INLINE与INLINABLE编译指示让GHC能够消除中间抽象,从而实现微乎其微的运行时开销。 (44:01)

Reading summary

John Wiegley argues that Haskell's lens ecosystem delivers immense practical ergonomics for navigating complex, nested data structures and monadic state, without requiring developers to master the underlying category theory. Drawing on research work at BAE Systems, he demonstrates that a compact working vocabulary of optics and operators can replace verbose boilerplate with declarative transformations. Core Optics and the Compact Operator Vocabulary (1:30) While the lens library has a reputation for mathematical complexity, Wiegley notes that practical development relies on a core vocabulary of roughly ten operators. Lenses target product types where a field is guaranteed to exist—such as tuple indices (`_1`, `_2`) or record fields derived via `makeLenses`—using `^.` (`view`) to read and `.~` (`set`) combined with `&` to update. Prisms address sum types where variants may be absent; they use `^?` (`preview`) to return a `Maybe` and treat mismatched updates as safe no-ops. These primitives compose seamlessly with standard function composition (`.`), turning nested record updates that normally require dozens of lines of manual reconstruction into readable, one-line declarations. Cardinality, Traversals, and Monoidal Aggregation (13:26) Moving beyond single-target lenses and zero-or-one prisms, traversals generalize optics to target zero, one, or multiple values across a data structure. The indexed optic `ix` focuses on specific elements in collections without throwing runtime errors if an index is missing, while `at` provides a full `Maybe` lens over map entries that can insert or delete keys. When viewing multi-target traversals, using `^.` requires the focused elements to have a `Monoid` instance to combine them into a single aggregate, whereas `^..` (`toListOf`) collects the visited targets into a standard list. Folds such as `allOf` and `anyOf` reuse these traversal paths as read-only analytical queries across complex structures. Integrating Optics with Monadic State and Zoom (24:03) The combination of lenses and `MonadState` bridges Haskell's purity with the intuitive ergonomics of imperative programming. Primitives like `use` (focused retrieval), `.=` (assignment), and `%=` (function modification) allow developers to manipulate nested structures inside a state monad cleanly. Wiegley highlights `zoom` as the architectural standout of this pattern: it isolates and executes an entire stateful sub-action within the narrow slice focused by a lens. By scoping state operations strictly to their relevant sub-components, developers can compose isolated domain building blocks into larger application environments without threading global state records through every function. Advanced Structural Rewriting with PartsOf and Biplate (31:32) For complex structural manipulation, specialized optics replace convoluted imperative algorithms with declarative expressions. The `partsOf` combinator exposes all targets of a traversal as a collective list, allowing developers to sort, filter, or reverse selected elements in place before seamlessly splicing them back into the original container. For deeply nested, heterogeneous data graphs like compiler ASTs, `biplate` borrows from Scrap Your Boilerplate techniques to target all sub-elements matching a specific type. Combined with `partsOf`, `biplate` can execute sweeping transformations—such as variable substitutions or reordering targeted typed values across nested records—without requiring code that accounts for every intermediate data constructor. Performance Characteristics and Architecture Choices (44:01) Wiegley emphasizes that the lens ecosystem carries little runtime performance penalty because combinators in Kmett's library are annotated with `INLINE` or `INLINABLE`, allowing GHC's optimization passes to boil away the high-level syntax. Regarding dependencies, he draws a sharp boundary between writing libraries and applications. For standalone public libraries, authors should avoid heavy dependencies by defining vanilla van Laarhoven functions or using minimal packages like `microlens`. Conversely, for internal application development, he advises depending directly on full `lens` to leverage its complete suite of operators, companion packages like `lens-aeson`, and state combinators without unnecessary friction. Wiegley concludes that while lightweight alternatives exist, teams building production applications should embrace full lenses for velocity. Because lenses are plain van Laarhoven functions under the hood, library authors can expose optic-compatible interfaces with zero dependencies on the core library, leaving downstream users free to integrate them as they see fit.

精炼阅读摘要

John Wiegley 指出,Haskell 的 Lens 生态系统无需深究范畴论即可在日常工程中发挥巨大价值,通过极少数核心操作符与组合子,便能极大简化深层嵌套数据更新、状态管理及通用结构遍历。 积类型与和类型的光学抽象基础 (1:30) Lens 专注于积类型中必定存在的子结构,既能映射到具体的在内存字段或元组位置(如 _1、_2),也能表达计算投影(例如将秒数时间戳转换为小时组件)。读取与写入分别通过 ^.(view)与 .~(set)操作符实现,配合反向函数应用操作符 &,开发者可写出清晰的流水线更新逻辑。通过 Template Haskell 的 makeLenses,深层嵌套记录的更新不再需要层层手动解包和重构,原本长达数十行的模式匹配样板代码可被缩减为单行组合链。 Prisms 与 Traversals 处理可选与多重目标 (9:03) Prism 针对和类型(如 Either 或自定义 ADT)设计,目标元素可能存在也可能不存在。使用 ^?(preview)访问若未命中则安全返回 Nothing,通过未命中的 Prism 赋值则表现为无害的恒等操作(no-op)。Traversal 则进一步将作用范围扩展至零个、一个或多个目标;其中 ix 提供了安全的基于索引的容器访问,而使用 ^. 查看多目标时默认需要目标具备 Monoid 实例进行折叠,或使用 ^.. 将所有匹配项抽取为纯列表。 容器映射与 MonadState 状态操控 (20:32) 在 Map 容器中,at 作为 Lens 将键值聚焦为 Maybe,赋值为 Nothing 可直接删除该键,搭配 non 还能便捷注入默认回退值;ix 则作为 Traversal 仅在键存在时就地更新。在 MonadState 语境下,use 替代了针对局部的 get 操作,.= 与 %= 则赋予了纯函数式状态类似于 C 语言般的就地赋值体验。架构层面上,zoom 允许将专注于局部状态的小型动作直接提升至更大的全局状态上下文中运行,实现了高度解耦的模块化状态设计。 高阶遍历:partsOf 与 biplate 的声明式转换 (31:32) 组合子 partsOf 能够将 Traversal 遍历出的所有离散目标提取为一个具体的物化列表,允许开发者直接应用 reverse 或 sort 等标准列表函数,随后再无缝将修改后的数据原位写回父结构。若替换列表长度不匹配,partsOf 会就地截断或静默忽略多余项。此外,基于 Generic 与 Typeable 的 biplate 组合子能够横跨深度嵌套的异构数据结构,一次性抓取或替换所有匹配特定类型的字段,在编译器 AST 的变量重写等场景中显著取代了繁琐的树遍历逻辑。 性能优化、依赖权衡与生态库架构实践 (44:01) Wiegley 澄清 Lens 在运行时几乎没有额外开销,因为 Edward Kmett 的 lens 库绝大部分组合子都带有 INLINE 或 INLINABLE 编译注解,高层语法糖在 GHC 优化编译期会被直接内联削减。在工程选型上,开发终端应用程序时应直接引入功能完整的 lens 库以获得完整生产力;但在开发面向全社区的基础开源库时,应优先编写无第三方依赖的原生 van Laarhoven 函数,或选用 microlens、picolens 与 lens-family-core 等轻量级替代方案,以避免引入沉重的构建依赖图。 Wiegley 认为开发者无需掌握复杂的理论即可通过掌握约十个常用操作符胜任生产开发,但对于想深入探究范畴论底层机制的学习者,他推荐参考 Simon Peyton Jones 在 Skills Matter 上的 Lens 入门讲座。

Detailed notes — zh

## Background 该视频由 YouTube 频道 Bay Area Haskell 于 2017 年 4 月 22 日发布,记录了 BAE Systems 研究员 John Wiegley 在 BayHac 2017 黑客松上的技术演讲。视频描述提供了大会维基页面链接以及作者存放演讲幻灯片 PDF 的 GitHub 仓库地址。 ## TL;DR John Wiegley 摒弃了复杂的范畴论推导,从工程实战角度系统梳理了 Haskell 中 Lens(透镜)生态的“杀手级应用”。演讲首先阐述了 Lens、Prism 与 Traversal 的本质区别与核心操作符,对比了嵌套数据结构在原生模式匹配与 Lens 组合下的代码表现力。随后,他重点演示了 Lens 与 Map、State Monad(以及 zoom)的结合方式,并进一步展示了 partsOf 和 biplate 等基于类型驱动的强大重构工具。最后,Wiegley 探讨了 Lens 借助 GHC 编译期内联所实现的零额外运行时开销,并为开源库与业务应用给出了务实的依赖选型建议。 ## Atmosphere & style 整场讲座风格平易近人、务实幽默,彻底打破了 Lens 充斥着晦涩数学理论和海量神秘操作符的高冷刻板印象。演讲者带着老练工程师的松弛感与现场观众轻松互动,即便遇到幻灯片贴错代码的小插曲也能风趣自嘲并引得全场哄笑。台下不时有 Edward Kmett 等社区大牛的影子被幽默提及,问答环节互动频繁,既有针对 AST 重构与性能优化的硬核探讨,又有“写应用程序谁在乎依赖,直接用就是了”的坦诚与洒脱,让整场技术分享既富有启发性又充满黑客松社区特有的极客活力。 ## Bulletin - [00:00](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=0s) BAE Systems 研究员 John Wiegley 指出,实际工程中并不需要死记成百上千个 Lens 操作符,熟练掌握 Lens、Prism、Map 和 State 的核心配合(约 10 个操作符)即可发挥其绝大部分威力。 - [10:04](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=604s) 针对和类型的 Prism 与遍历多目标的 Traversal 具备天然的容错能力:当模式不匹配或索引越界时,读操作返回安全回退值,写操作则静默保持原样(no-op),避免了层层嵌套的原生模式匹配与解包逻辑。 - [26:22](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1582s) 通过 zoom 组合子,大型应用能将状态单子拆分为仅关注局部小状态的模块化积木;同时 partsOf 与 biplate 等高阶工具允许开发者将复合数据切片提取为虚拟列表甚至纯类型驱动定位,实现复杂的原位批量变换。 - [44:01](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2641s) 由于 Lens 库中绝大多数组合子均标有 INLINE 或 INLINEABLE,抽象层开销在 GHC 编译期几乎全部被消除;对于应用开发可直接引入 lens 主包,开源库开发者则建议采用无依赖的纯函数形式定义透镜。 ## Key takeaways - [04:00](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=240s) Lens 在工程实践中的核心在于解决深层嵌套结构的读写痛点,避免手动解包与重新打包带来的大量管道胶水代码。 - [01:30](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=90s) Lens 关注积类型中必然存在的字段,通过 view(^.)和 set(.~)提供直达深层的读取与写入抽象,且可透明支持秒转换为时分秒等动态计算结构。 - [06:55](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=415s) 日常开发仅需约 10 个核心操作符(如 view、set、?~、%~ 等),开发者无需掌握复杂的类型论即可借由模式模仿快速上手。 - [09:03](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=543s) Prism 专门面向和类型或可能不存在的分支;使用 preview(^?)进行安全访问,在写入不匹配分支时会自动退化为无操作(no-op)。 - [13:14](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=794s) Traversal 将目标扩展到 0 到多个元素,例如索引访问器 ix;针对越界写入直接静默忽略,避免抛出运行时异常。 - [16:20](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=980s) Traversal 与 Monoid 深度绑定:使用 ^. 查看遍历结构会依赖 mappend 静默聚合元素,使用 ^..(toListOf)则能完整收集为列表。 - [19:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1172s) Iso(同构)表达两个数据类型的无损等价转换,例如在管道内部利用 lazy 无缝切换严格与惰性 ByteString,免去显式转型步骤。 - [21:14](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1274s) 在 Map 操作中,at 作为 Lens 允许通过 Just 与 Nothing 实现插入、更新与删除,配合 non 能够优雅注入缺省默认值。 - [22:53](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1373s) at 与 ix 在 Map 写入语义上存在本质差异:at 遇到不存在的键会新建记录,而 ix 仅在键已存在时更新,缺失时直接跳过。 - [24:03](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1443s) 结合 MonadState 后,Lens 衍生出 use、.= 与 %= 等操作符,提供接近命令式语言赋值体验的同时兼具类型安全。 - [26:22](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1582s) zoom 改变了函数式状态架构的设计范式,允许细分函数仅针对局部子状态编程,再灵活提升(lift)并嵌套进复杂的全局主系统状态。 - [31:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1892s) partsOf 将 Traversal 选中的所有元素打包暴露为虚拟列表,支持直接排序、反转或重排后原位写回原结构,多余或不足的槽位均被安全处理。 - [35:54](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2154s) 基于 Uniplate 思想的 biplate 允许纯粹以数据类型为导向递归扫描整棵深层 AST 或复杂嵌套类型,极大简化了编译器的批量重写逻辑。 - [44:01](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2641s) GHC 的激进内联机制能够在编译期将大部分 Lens 组合子消除展开为底层模式匹配,因此引入 Lens 几乎不会带来额外的运行时性能退化。 - [45:10](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2710s) 公共开源库推荐采用无外部依赖的纯函数签名实现 Lens,而私有业务应用则建议直接引入功能完善的 lens 生态以加速交付。 ## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=0s) 讲座定位与 BAE Systems 的工程实践 在 BayHac 2017 的现场,来自 BAE Systems 的研究员 John Wiegley 开门见山地明确了整场演讲的基调:这不是一场充满理论推导的讲座。他不打算深挖 Lens 的底层类型推导(如 profunctor 或 van Laarhoven 编码),也不会把成百上千个操作符逐一罗列出来。“这次演讲不会预设你具备任何关于 Lens 的使用经验或理解,”John Wiegley 说道。 谈及在公司的工作背景,John Wiegley 表示团队在许多研究型项目中广泛采用 Haskell,而 Lens 更是无处不在。他特别幽默地打消了大家的刻板印象:团队并不是因为“有某个自以为是的家伙觉得每个项目都非得塞进 Lens 不可”才采用它,而是因为“团队中的每一个人都真真切切地体会到了 Lens 的强大威力”。比起网上海量的教程和博客,他更想传授工程实践中淬炼出的“杀手级应用”(killer apps)。 ```mermaid flowchart TD accTitle: Lens Core Learning Roadmap accDescr: 展示从基础 Lens 与 Prism 到核心组合 Traversals、Map 和 State 的学习路径 subgraph Basics["基础阶段"] L["Lens (积类型 / 总是存在)"] P["Prism (和类型 / 可能不存在)"] end subgraph KillerApps["杀手级核心组合"] T["Traversals (多目标遍历)"] M["Map (键值映射操作)"] S["State (状态单子结合)"] end L --> T P --> T T --> M M --> S ``` - 演讲涵盖的四大核心要素:Lens、Prisms、Traversals,以及结合 Map 和 State 的操作模式。 - John Wiegley 强调只要熟练掌握 Lens、Prism、Map 和 State 这四者的配合,就已经发挥出 Lens 库绝大部分的杀手级功效,其余高级特性纯属可选。 --- ### [01:30](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=90s) Lens 的本质与元组操作(View 与 Set) John Wiegley 深入阐述了 Lens 的本质:与后续要讲的结构不同,Lens 专门用来定位和操作**结构中必然存在**的某个组成部分。这通常对应代数数据类型中的积类型(Product Type,如元组或记录结构)。在包含多个元素的集合中,位于首位的元素永远存在,因此 Lens 允许你直达、观察(view)或设置(set)该字段。他特意给“结构”(Structure)一词加上了引号,指出它不一定是内存中静态分配的数据结构,也可以是纯粹计算性的结构。例如将以秒为单位的时间转换为“小时、分钟、秒”,其中“小时”就是一种计算层面的结构切片;Lens 可以在底层透明地完成往返计算,让你像操作普通字段一样修改秒数里的“小时”。 在 Haskell 的标准 Lens 库中,最基础的形式是操作元组的透镜(例如针对三元组第二项的 `_2`,它针对任意具有积类型特征的数据进行了重载)。在读取与写入操作上,常用语法如下: - 读取数据:使用操作符 `^.`(即 `view`)。例如三元组取第二项写作 `tuple ^. _2`,也可以显式调用函数 `view _2 tuple`。在需要使用 View Patterns 进行模式匹配时,函数形式极为方便,例如 `view _2 -> pattern`。 - 写入数据:使用反转函数应用操作符 `&`(提供管道式体验)配合 `.~`(即 `set`)。例如修改第二项写作 `tuple & _2.~ newValue`,等价于 `set _2 newValue tuple`。 面对台下观众对操作符复杂度的担忧,John Wiegley 笑着安抚说平时用多了自然会适应:“我平均只使用大约 10 个操作符,今天讲座展示的也就这 10 个左右,其中用得最多的核心操作无非就是 view 和 set。” --- ### [04:00](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=240s) Record 记录结构与嵌套层级痛点 John Wiegley 坦率地指出:“记录结构(Records)可能是使用 Lens 最无趣的方式,但它却总是作为 Lens 教程的入门招牌来演示。”如果一个工程中的记录结构非常扁平浅显,字段都只是简单的基础类型,强行引入 Lens 反而显得繁琐甚至得不偿失。他坦言自己也走过弯路,曾经在一些小项目中“为了用 Lens 而用 Lens”,最后发现明明原生的 Record 语法就已经够用了。 然而,记录结构的真正优势在数据层级变深时才会爆发。记录本质上是有具名字段的积类型,在工程中通过开启 `TemplateHaskell`、引入 `Control.Lens`,并在字段名称前加下划线前缀(如 `_field1`、`_field2`),再调用 `makeLenses ''MyRecord`,编译器就会在编译期自动生成移除了下划线的同名 Lens 函数。 ```mermaid flowchart LR accTitle: Pure Haskell vs Lens Deep Record Update accDescr: 对比原生深层记录更新的层层解包打包与 Lens 的直接管道更新 subgraph PureHaskell["纯 Haskell 方式"] direction TB A1["解包最外层 Record"] --> A2["解包中间层 Record"] A2 --> A3["修改最深层字段"] A3 --> A4["重构中间层 Record"] A4 --> A5["重构最外层 Record"] end subgraph LensWay["Lens 组合方式"] direction TB B1["深层目标字段"] -->|"record & subA. subB. field +~ 1"| B2["自动管道解构并重构"] end ``` - 原生深层修改的困境:如果在深层嵌套中给某个数值加 1,开发者必须显式解构最外层、中间层并层层包裹回去,代码中充满为了维持其余字段不变而写的手动管道代码(plumbing)。 - Lens 的优雅组合性:利用 Lens 的函数复合能力,可以通过类似 `record & subA. subB. field +~ 1` 的形式直接钻入深层。 - 复合思维模型转变:“一旦你开始为数据类型建立起一套透镜词汇体系,你就可以随意构建任意复杂的链条;在脑海中你只需思考‘我该如何遍历它’,而不是‘我该如何层层拆包打包以维持计算正确性’。” --- ### [06:55](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=415s) 常用操作符集锦与手动编写 Lens 在与现场观众互动解答关于函数修改的问题时,John Wiegley 解释了如何对透镜指向的值应用任意函数。这引出了日常开发中最核心的符号体系: | 操作符 / 函数 | 功能与含义 | 典型应用场景 | |:--- |:--- |:--- | | `^.` (`view`) | 读取目标透镜的值 | 从深层结构或元组中读取特定字段 | | `.~` (`set`) | 设置目标字段为指定值 | 覆盖替换字段的原有值 | | `?~` | 将目标值包装为 `Just` 并写入 | 针对 `Maybe a` 字段,省去显式写 `Just x` | | `+~`, `-~`, `*~` 等 | 对字段执行对应数学计算 | 例如递增数值、做乘法缩放 | | `<>~` | Monoid 拼接操作 | 向结构内的列表追加元素或合并 Map | | `%~` (`over`) | 对目标字段应用普通函数 | 执行任意自定义变换函数(如翻转字符串) | | `%%~` | 对字段应用带有 Applicative 效应的函数 | 让副作用或上下文效应向外序列化传递 | John Wiegley 提示大家不必死记硬背幻灯片中手写 Lens 的模式,这主要是作为日后的参考。“通过照猫画虎(cargo culting)是上手 Lens 的好方法。”在无法通过既有 Lens 组合出目标时,可以手写一个接受 Applicative 箭头的普通函数,深入结构内部应用效应,再通过 `sequence` 将计算效应向外冒泡传递。 --- ### [09:03](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=543s) Prism 概念引入与和类型处理 讲座的后半段自然过渡到了 Prism(棱镜)。John Wiegley 梳理了 Lens 与 Prism 之间的对偶关系: - **Lens** 处理的是**积类型(Products)**的内部字段,对应关系是“必定存在”; - **Prism** 处理的是**和类型 / 协积类型(Co-products / ADTs)**的可能性分支,对应关系是“可能存在”。 以基础类型为例,元组的第一项必定存在,因此用 Lens 提取;而 `Either a b` 的 `Left` 分支可能存在也可能不存在,因此提取它必须使用 Prism。“如果说元组是积类型的代表,那么 `Either` 就是 Prism 最具代表性的范例。” 定义用户自己的 Prism 非常简单:正常编写代数数据类型(ADT,如包含 `Alpha`、`Beta`、`Gamma` 等构造器),无需额外的下划线命名约定,直接使用 Template Haskell 的 `makePrisms ''MyType` 即可。按照官方惯例,生成出的 Prism 会以下划线开头(如 `_Alpha`、`_Beta`),使它们可以与其它 Lens 和 Prism 平滑地以点号(`.`)链式组合使用。 ### [10:04](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=604s) Prism 的使用:安全访问、安全更新与无操作特性 John Wiegley 演示了如何提取并修改复合数据类型构造器内部的字段。对于像 `Alpha` 这样可能存在也可能不存在的构造器,查看其字段时不能使用常规的查看操作符 `^.`,而必须使用 `^?`(即 `preview`)。这是因为目标结构可能并不是 `Alpha`;如果目标匹配成功,则返回被 `Just` 包裹的值;若遇到不匹配的构造器(如 `Gamma`),`^?` 则安全地返回 `Nothing`。 更引人瞩目的是写操作的行为。当尝试将一个 `Alpha` 的第二个字段设置为 `2` 时,如果传入的值确实是 `Alpha`,它会精准执行更新;但如果传入的是其他构造器(例如 `Gamma`),该修改操作并不会崩溃,而是直接被忽略,成为一次静默的无操作(no-op)。John 指出,这种语义在编写通用算法时极其强大:开发者可以针对某种特定的数据形态编写处理逻辑,当外部传入符合条件的数据时正常执行,不符合时则自然跳过,无需编写繁琐的防御性分支判断。当观众问及是否能让不匹配的写操作直接抛出错误时,John 笑着回应自己从未这么做过,但调侃道:“既然是在黑客松,你完全可以自己试试看会发生什么。” ```mermaid flowchart TD accTitle: Prism 读写行为分支图 accDescr: 展示 Prism 在遇到匹配结构与不匹配结构时读写操作的不同行为 Input["输入数据结构"] --> Check{"构造器是否匹配?"} subgraph MatchBranch["匹配成功 (如 Alpha)"] Check -- "是" --> ReadSuccess["^? 返回 Just value"] Check -- "是" --> WriteSuccess[".~ 执行字段更新"] end subgraph MismatchBranch["匹配失败 (如 Gamma)"] Check -- "否" --> ReadFail["^? 返回 Nothing"] Check -- "否" --> WriteFail[".~ 无操作 (静默保持原样)"] end ``` - `^?` 操作符专用于可能失败的目标查看(即 Prism 或 Traversal),结果类型为 `Maybe a`。 - 使用 Prism 进行设值时,若目标构造器不匹配,操作会自动退化为恒等变换(no-op)。 --- ### [11:29](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=689s) 代码表现力对比:Lens/Prism 组合与纯手写模式匹配 为了说明引入光学库的必要性,John 对比了在结构深处将一个值加 1 的两种实现方式:一种使用 lens 与 prism 组合,另一种则使用原生 Haskell 手写。原生写法非常痛苦:首先需要用 `case` 语句对外部构造器进行模式解构,确认是否匹配;接着拆解内部记录;递增对应字段;最后再一层层将记录与外层构造器重新封装打包回去。当遇到嵌套了 `Map`、记录类型与求和类型的复杂结构时,原生 Haskell 代码的膨胀速度呈爆炸式增长。 John 分享了一段工作中的真实轶事:“曾几何时,我面对一个包含 Map、Map 里又包含记录的复杂结构。为了向我的同事证明 lens 的表现力有多强,我特意手写了对应的原生 Haskell 代码——整整写了 20 行,而它们做的事情,lens 仅仅用了一行就搞定了。”这就是 lens 带来的“努力收益比”:嵌套越深、组合越复杂,lens 节省的代码量与心智负担就越惊人。 针对观众关于“传错操作符导致类型错误难以阅读”的提问,John 解释道,由于整个库基于类型类(Type Classes)构建,组合各个操作符本质上是在汇聚并推导约束条件,系统会退化到表达该操作所需的最严苛约束。一旦用错了操作符,编译器便会报错提示缺少某种类的实例,而此类错误信息往往非常晦涩,“不幸的是,你往往很难一眼看出自己到底写错了什么,但你至少知道自己肯定搞砸了”。 --- ### [13:14](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=794s) 从 Prism 走向 Traversal:`ix`、`preview` 与动态计算结构 John 阐明了手写 Prism 的本质:只需要提供一对映射关系——如何从内部类型构建出外部类型,以及如何解构外部类型以提取目标部分。随后,他引出了 Traversal(遍历)的概念。Prism 聚焦的是“0 个或 1 个可能存在的值”,而 Traversal 瞄准的则是“0 个、1 个或多个可能存在的值”(例如列表中的每一个元素)。 许多看起来像 Prism 的地方,底层其实是 Traversal。例如索引访问器 `ix`,使用 `preview`(即 `^?`)配合 `ix 1` 访问列表时,返回的是索引为 1 的第二个元素。如果索引存在,返回 `Just 2`;若越界,则返回 `Nothing`。设值操作同样遵循这一安全原则:更新越界位置会直接忽略。面对“为什么 `ix` 是 Traversal 而不是 Prism”的疑问,John 打趣说可以去问台下的 Edward Kmett(被他誉为“lens 界的尤达大师”)。 ```mermaid flowchart LR accTitle: 光学目标基数对比 accDescr: 对比 Lens、Prism 与 Traversal 指向的目标数量差异 subgraph LensBox["Lens"] LTarget["精准指向 1 个元素"] end subgraph PrismBox["Prism"] PTarget["指向 0 个或 1 个元素"] end subgraph TraversalBox["Traversal"] TTarget["指向 0 个、1 个或多个元素"] end LensBox -.->|"弱化为"| PrismBox PrismBox -.->|"推广为"| TraversalBox ``` 为了展示 Traversal 的灵活性,John 编写了一个名为 `digits` 的自定义 Traversal,它可以将一个整数转换为其各个位数的列表。通过 `pi ^? digits. ix 2`,可以精准提取圆周率百位/对应索引位置上的数字(返回 `Just 4`)。更令人称奇的是写入操作:如果将第三位数字更新为 `99`,它会直接扩展该数字的实际位宽来容纳新值。尽管 John 承认该实现可能并不严格满足 Lens 法则(类似于 Python 的切片赋值,遇到特定极端用例可能会破坏一致性),但它淋漓尽致地展现了 Traversal 将纯计算过程包装为可变结构的威力。 - `ix n`:支持列表、Map 等结构的索引访问 Traversal,越界自动处理为安全无操作。 - 自定义 Traversal(如 `digits`)不仅可以解构固定内存布局,还能将动态计算过程抽象为可读写的容器。 --- ### [16:20](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=980s) Traversal 与 Monoid 的碰撞:`^.`、`^..` 与 Fold 聚合 当对一个 Traversal 使用常规的读取操作符 `^.` 时,新手常会遭遇令人困惑的类型错误:编译器报错提示缺少 `Monoid` 实例。John 强调,必须理解 Traversal 与 Monoid 的内置关联:当你用单一视角(`^.`)去查看一个可能指向多个目标的 Traversal 时,lens 默认会使用 `mappend` 将遍历到的所有元素拼接为一个值。 如果想将 Traversal 遍历到的所有元素保留下来,正确的操作符是 `^..`(对应 `toListOf`)。它的内部机制极为巧妙:把看到的每个元素包装成单元素列表(Singleton list),再借助列表自带的 Monoid 实例将其拼接为一个完整列表。此外,配合 `to` 组合子(可将任意普通函数提升为 Getter),可以在遍历中途执行数据映射与 Monoid 累加:例如使用 `traverse. to Sum` 并配合 `^.`,就能直接对列表所有元素求和。 从 Traversal 进一步延伸,便得到了 Fold。Fold 可以看作“只读版的 Traversal”,无法执行反向写回。Lens 库中提供了体量极其庞大的 Fold 工具集。例如 `allOf`,它接受一个 Traversal 和一个谓词函数,判断遍历出的所有元素是否均满足条件。在判断“元组列表里的所有第二个元素是否均为偶数”时,虽然纯原生函数组合也能做到,但当开发者手中已经持有一个深层 lens 时,直接套用现成的 Fold 组合子能极大地免去繁琐的手工适配。 “在 Lens 库里,我所见过的体量最大的组件类别大概就是 Fold 了,”John 总结道,“它为你构建了一套非常庞大的词汇库,让你在拥有深层 lens 的同时,能够随心所欲地执行日常习惯的各种聚合操作。” - `^.` 应用于 Traversal 时,依赖目标类型的 `Monoid` 实例进行静默聚合。 - `^..`:将 Traversal 提取到的所有零散项整合成一个标准列表。 - `Fold`:单向只读的流式提取与判断工具(如 `allOf`、`anyOf`、`sumOf` 等)。 --- ### [19:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1172s) 光学体系核心词汇梳理:Lens、Getter 与 Iso 为了帮助大家理清 Hackage/Haddock 文档中密密麻麻的类型签名,John 对核心光学概念的层级进行了归纳梳理。各种抽象根据其具备的能力与约束,构成了严密的关系网: | 光学概念 (Optic) | 目标数量 | 访问权限 | 核心特性与用途 | |:--- |:--- |:--- |:--- | | **Lens** | 恰好 1 个 | 读写双向 | 最基础的双向透镜,聚焦单一确定字段。 | | **Getter** | 恰好 1 个 | 仅只读 | 限制较 Lens 更少,可用 `to` 将任意函数提升为 Getter。 | | **Prism** | 0 个或 1 个 | 读写双向 | 面向求和类型,处理可能不存在或构造器失配的情况。 | | **Traversal** | 0 到任意多个 | 读写双向 | 面向复合结构,批量寻址内部所有满足条件的目标项。 | | **Fold** | 0 到任意多个 | 仅只读 | Traversal 的只读形态,专注于提取、统计与 Monoid 归约。 | | **Iso** (同构) | 恰好 1 个 | 完全双向等价 | 表达同构转换,可毫无损耗地来回切换不同数据表征。 | 谈及 Iso(Isomorphism)的直观理解时,John 解释道,它代表“同一事物的另一种等价表现形式”。在 Iso 的视野里,A 类型可以无损转换为 B 类型,B 类型也可以毫无保留地还原为 A 类型。他在现场举了一个极为实用的工程案例:在 ByteString 的严格(strict)与惰性(lazy)形态之间切换。标准库提供了一个名为 `lazy` 的 Iso,在长链式的 lens 组合中,只需直接插入 `. lazy.`,就能在深度遍历管线的正中间,顺滑地将严格 ByteString 当作惰性 ByteString 进行后续操作,省去了外部来回手动转型的冗余胶水代码。 ### [20:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1232s) Traversal 操作符与 Map 的引入 John Wiegley 承接前文对 Prism(棱镜)与 Traversal(遍历)的区分:Prism 可能可读可写,对应 0 个或 1 个元素;而 Setter 与 Traversal 作用于多个元素,涵盖 0 个或更多元素。在遍历多元素时,最常用的是 view 系列操作符,例如 `^.`(view)和极其高频的 `^?`(preview)。此外还有强行索取的 `^?!`,其语义是明确知晓目标存在,如果不存在则直接抛出运行时错误。John 坦言,当开发者能在脑海中严密推导代码并确信元素必定存在时,使用 `^?!` 确实很方便。 随后,John 将话题切入 Map 数据结构,并感叹道:“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.”(Map 是真正把我推进 Lens 大门的东西,因为你能用它对 Map 做的那些操作,意味着你再也不需要死记硬背那一堆特定函数了。) ### [21:14](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1274s) Map 操作核心:`at`、默认值 `non` 与幻灯片插曲 在 Map 中提取元素最核心的 Lens 访问器是 `at`,它的返回值被包裹在 `Maybe` 中。例如使用 `^. at 1`,若存在则返回 `Just v`,不存在则返回 `Nothing`。`at` 的强大之处不仅在于查询,更在于赋值:若将某键通过 `.~` 赋值为 `Just x`,它会自动插入或更新该键;若赋值为 `Nothing`,则等价于直接删除该键。 讲到此处,现场出现了一个关于幻灯片代码的趣味插曲。John 发现幻灯片上的示例输出写错了,本应演示 `at 3` 得到 `Nothing`,幻灯片里却依然写着原本的值。他笑着向台下解释,自己确实为每个示例都编写了自动化测试,但制作幻灯片时是通过名称引用的,结果贴错了地方,引来现场一片笑声。台下听众随即打趣:“所以在这个演讲上用个 Lens 吧,那样一切就全对了。” 为了避免每次使用 `at` 都要手动处理 `Maybe`,`lens` 库提供了 `non` 组合子。`non` 能为缺失值注入默认回退项:如果目标存在,它就取出该值;如果不存在(即遇到 `Nothing`),则返回传递给 `non` 的参数。例如 `at 2. non 'z'` 会直接返回原值,而 `at 3. non 'z'` 在找不到时会退回 `'z'`。John 强调 `non` 赋予了一种极其优雅的设置默认值机制。 - `at k`:全能 Lens,Getter 返回 `Maybe a`,Setter 传入 `Just` 为插入/更新,传入 `Nothing` 为删除。 - `non d`:将 `Maybe a` 同构映射为 `a`,将 `Nothing` 视为默认值 `d`。 ### [22:53](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1373s) `at` 与 `ix` 的关键区别及 `failing` 在讲解完 `at` 后,John 将其与另一个常用访问器 `ix` 展开横向对比。`ix` 本质上是一个 Traversal,不仅适用于 Map,也适用于列表、Vector 等索引结构: - **读取时的差异**:`^. at 1` 返回 `Maybe a`;而 `ix 1` 作为一个 Traversal,通常使用 `^? (ix 1)` 访问,同样返回 `Maybe a`。如果使用强断言 `^?! (ix 1)`,遇到不存在的键就会报错。 - **写入时的语义鸿沟**:如果执行 `at 1.~ Just 'c'`,当键不存在时会强制创建该键;若执行 `at 1.~ Nothing` 则会删除该键。但如果使用 `ix 1.~ 'c'`,由于它是一个 Traversal,它只会在该键**已经存在**时更新其值;如果该键本就不存在,它将完全不执行任何操作(静默跳过),绝不会像 `at` 那样凭空插入新键。 - **回退机制的不同**:伴随 `at` 的是提供默认值的 `non`;而伴随 `ix` 的则是 `failing` 组合子。`failing` 的语义是“优先尝试第一个 Traversal,如果未能命中任何目标,则尝试下一个 Traversal”。 John 总结道,正因为这两种行为模式在实际业务中各有不可替代的用途,他在实际工程中两者都会频繁使用。 ```mermaid flowchart TD accTitle: at 与 ix 在 Map 写入时的行为差异 accDescr: 决策树展示了使用 at 和 ix 写入 Map 时,键存在与不存在两种分支下的不同处理逻辑。 Start["Map 写入操作"] --> Choice{"选择访问器"} subgraph AtBranch["at 访问器 (Lens)"] Choice -->|"使用 at k"| AtVal{"写入的值"} AtVal -->|"Just v"| AtInsert["存在则更新 / 不存在则插入"] AtVal -->|"Nothing"| AtDelete["存在则删除 / 不存在则无操作"] end subgraph IxBranch["ix 访问器 (Traversal)"] Choice -->|"使用 ix k"| IxExist{"Key 是否存在?"} IxExist -->|"存在"| IxUpdate["更新对应 Value"] IxExist -->|"不存在"| IxNoop["什么都不做 (静默忽略)"] end ``` ### [24:03](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1443s) Lens 与 State Monad 的结合:类 C 语言的命令式语法 随后,John 展示了 Lens 与状态单子(`MonadState`)结合带来的革新体验。在传统的 `State` 中,开发者通常调用 `get`、`put` 或 `modify`,但在引入 Lens 后,代码风格发生了翻天覆地的转变: ```haskell -- 获取与查看 use _1 -- 相当于 get 并在状态(如 (10, 20))上应用 Lens _1,获取 10 uses _1 f -- 获取目标后,在输出时应用函数 f preuse (ix 1) -- 针对 Prism/Traversal 的安全查看,返回 Maybe preuses (ix 1) f -- 针对 Prism/Traversal 并在返回时应用函数 -- 修改与设置 _1.= 5 -- 将目标设置为 5 _1 %= (+1) -- 类似于 modify,在状态的原地应用函数修改 ix 1.= 5 -- 若列表索引存在则设为 5,不存在则什么都不做(绝不抛出异常) ``` John 饶有兴致地指出,诸如 `ix 1.= 5` 这种语法让 Haskell 写起来甚至“开始变得非常像 C 语言”(begins to look a lot like C)。若当前状态是一个四元素列表,执行 `ix 1.= 5` 会将第二个元素更新为 5;即便列表长度不足,它也会安全地静默跳过,不会触发运行时错误。 此外,Lens 还提供了 monadic 赋值操作符(如带波浪线的绑定符号),在结合 `StateT` 时极为有用。面对听众对示例代码上下文的疑问,John 解释道,这些操作符都是构建在 `MonadState` 上的单子动作,外层可以通过 `execState` 来执行。他还补充了一个有趣的技巧:标准库函数 `id` 本身是一个同构(Iso),因为任何事物都与自身同构;因此若状态本身就是一个裸数字,直接写 `id %= f` 就能直接对其进行函数变换。 ### [26:22](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1582s) `zoom`:超越局部状态的组合威力 在整个状态操作的生态中,John 重点推崇了 `zoom` 函数。`zoom` 接收一个 Lens 和一个作用于子状态的单子动作,它的魔力在于:在执行该子单子动作时,它会将外部的大状态临时“聚焦”并收缩为 Lens 所指向的小子状态。 有现场听众提问:“这难道不总是等价于直接组合两个 Lens 吗?”(Is that always equivalent to just composing the two lenses?) John 给予了否定的辨析:如果在一个作用域内需要连续执行多个动作,只靠组合 Lens 你就必须在每一处代码里一遍又一遍地重复书写长串的组合路径。而 `zoom` 的真正价值远不止于此,它彻底改变了函数式状态架构的设计范式: > “Where zoom kind of really comes into its own is where you have an application you want to be really principled... 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.” > —— John Wiegley 在大型应用中,开发者为了架构纯粹性,往往倾向于使用严谨的状态单子而非到处乱扔 `IO`。但这经常导致一个痛点:所有业务状态被硬塞进一个巨大的 State 产品类型中,很多细分函数不得不接触自身根本不需要关心的冗余状态。通过 `zoom`,开发者可以编写只关注自身局部状态(例如一个单纯的二元组)的高度模块化、轻量级函数;随后利用 `zoom`,将这些精悍的积木自由提升(lift)并嵌套进更大的三元组、四元组或由复杂 Record 构成的主系统状态中,极大地提升了组件的复用性与封装性。 ### [29:04](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1744s) 记录连续赋值与未尽特性一览 在需要对 Record 进行密集赋值的场景下,Haskell 开发者通常使用 `&` 运算符层叠 Setter,代码形式如 `record & fieldA.~ valA & fieldB.~ valB`,视觉上类似命令式的字段赋值。而结合状态单子,Lens 还提供了 `&~` 语法糖(`execState` 的简写形态):在 `&~` 右侧直接开启一个 `do` 块,内部利用 `.=` 和 `%=` 对状态变量像命令式脚本一样逐行赋值,进一步拓宽了语法表达力。 在分享的尾声,John 快速盘点了本次分享未及展开的高阶与外围特性: - **Writer 交互**:类似于 State 拥有 `zoom`,Writer 单子也有专属的类似聚焦机制。 - **`lens-action`**:支持具有单子结果的 Lens 操作,尽管日常业务中较少使用。 - **`lens-aeson`**:John 特别强调了此库在其公司的核心地位:“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. It begins to look like XPath really for dealing with anything.”(就是它,lens-aeson,让我在公司内部向那些之前没接触过 lens 的人推广 lens 变得无比容易;处理任意 JSON 数据时它用起来简直就像 XPath 一样自然。) - **`thyme`**:提供全面 Lens 访问器的时间操作库。 - **Indexed Lenses**:在遍历容器时能够将当前元素的索引或键随同值一并暴露给调用方。 - **Zippers**:允许开发者通过 Lens 深入复杂结构后,在局部节点之间横向漫游、迭代爬梳,而无需每次都重新自顶向下寻找相邻子对象。 - **生态外延**:异常处理(Exception Lenses)、数组与 Vector 操作、FilePath 路径处理等广泛的官方与社区衍生工具。 ### [31:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1892s) 用 `partsOf` 将 Traversal 映射为列表进行结构性变换 在结束了数值转换(如十六进制转八进制等操作)的讨论后,John Wiegley 准备展示一个极其震撼的特性。他幽默地提醒观众:“如果想保住自己的理智,就离这些幻灯片远点(And if you want to keep your mind, just stay away from these slides)。”他随后引入了 `partsOf`:普通的 traversal 允许我们遍历数据结构中的多个元素,例如 Map 中的所有值;而 `partsOf` 能够将一个 traversal 瞄准的所有元素“打包”并以列表(List)的形式暴露出来。使用者可以直接对这个列表的结构进行任意修改(如翻转、排序、替换),修改完成后,`partsOf` 会自动将这批元素按照原有的遍历位置写回原数据结构中。 ```mermaid flowchart LR accTitle: partsOf 工作原理 accDescr: 展示 partsOf 如何提取元素为列表、修改后写回原位置的过程 subgraph Step1["1. 提取匹配项"] A["原数据结构 (列表/元组/AST)"] -->|"Traversal (如 indices odd)"| B["元素提取为虚拟 List"] end subgraph Step2["2. 列表结构变更"] B -->|"执行 List 操作 (reverse / sort)"| C["更新后的 List"] end subgraph Step3["3. 原位缝合写回"] C -->|"partsOf 原位写回"| D["最终修改后的结构"] end ``` 通过第一个代码示例,John 演示了带有索引信息的遍历函数 `traversed`(带字母 `d` 的形式)。使用 `traversed` 可以在后续通过 `indices` 约束遍历范围;如果只用普通的 `traverse`,就无法在右侧组合 `indices`。在这个例子中,目标是提取列表中所有处于奇数索引位置(即 1, 3, 5 等位置)的元素,然后将这些奇数位上的元素反转(`reverse`),再放回原位。虽然底层操作非常精妙,但写出来的代码却高度声明式且直观易读。 关于 `partsOf` 的边界行为与进阶用法: - **元素数量不匹配的处理**:若通过 `partsOf` 赋值一个新列表,如果提供的新元素较少,它只覆盖前面的槽位,多余的原结构元素保持不变;如果提供的新元素过多,超出的部分会被直接忽略,不会抛出运行时错误。 - **条件过滤与排序**:无需索引时可直接使用 `traverse`。例如对列表中所有小于 4 的元素使用 `filtered (< 4)` 过滤,结合 `partsOf` 将其提取出来进行就地排序并反转。John 感叹道:“你能想象在纯 Haskell(不借助 lens)里写这个要费多大劲吗?(Could you imagine trying to write this in plain Haskell?)”使用 lens 可以直接声明“我要把这个列表里所有小于 4 的部分就地排序并反转”。 - **开源代码参考**:演讲幻灯片以及配套代码已托管在 GitHub(jwiegley 账号下的首个仓库),名为 `lenses.hs`,里面包含了大量超越讲座演示的实战案例,甚至是他日常开发查询的速查备忘录。 --- ### [34:32](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2072s) `partsOf` 与 `each` 及字符串模板的高级结合 John 接着演示了 `partsOf` 的更多组合可能。首先是结合 `each` 处理异构或同构的大型元组(Tuple)。`each` 是遍历元组中每一个成员的 traversal,与 `partsOf` 组合后,开发者可以把一个庞大的元组当成普通的列表来看待,直接对元组里的元素执行排序。如果要在标准 Haskell 中手写针对元组各个字段的排序函数,代码量将极其庞大且难以维护。 ```mermaid flowchart TD accTitle: partsOf 实战应用场景 accDescr: 展示 partsOf 配合 each、字符串过滤及时间模板覆写的具体应用 A["partsOf 应用场景"] --> B["元组元素排序 (搭配 each)"] A --> C["字符串局部字母覆盖"] A --> D["格式化模板精准填充"] B --> B1["将 Tuple 视为 List 进行 sort"] C --> C1["仅匹配 isAlpha 字符并覆写 Howdy"] D --> D1["跳过前3位并将 0 替换为目标数字"] ``` 随后,他展示了字符串就地覆写的示例:通过 traversal 匹配字符串 `"Hello World"` 中的所有字母字符(`isAlpha`),并用新字符串 `"Howdy"` 覆盖。结果是仅有前五个字母位置被覆写为 `"Howdy"`,而原字符串的空格及其他非字母字符完全保持在原位。 在时间模板的例子中,假设有一个时间占位符模板,目标是精准覆盖分钟和秒数区域。通过 lens 设定条件:查找位置大于等于 3(`indices (>= 3)`)且内容为字符 `'0'` 的所有槽位,将目标数字写在上方。Lens 在此处扮演了高维“寻址工具”的角色,将原本繁复的手动字符串切片、拼接与替换逻辑压缩为几行干净、富有表现力的代码。 --- ### [35:54](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2154s) 终极重型武器:`biplate` 类型驱动遍历 鉴于还有剩余时间,John 决定介绍一个“比 `partsOf` 更具冲击力”的工具——`biplate`。他开玩笑地坦白道:“我在实际工程中其实还没用过这个,但你不能不知道 biplate,而且你也绝不会想知道 biplate 是怎么实现的(You can't not know about biplate, and you don't want to know how biplate's implemented either)。” `biplate` 是一种强大的多态 traversal,源于 Uniplate 思想。它能递归扫描极其复杂且深层嵌套的任意数据结构(如混合了嵌套元组、列表、自定义数据类型的结构),并纯粹依据目标类型(Type)将结构内所有对应类型的值提取出来。针对现场观众关于类型约束的提问,John 确认其依赖类似 `Typeable` 或 `Data` 的机制,而在 GHC 7.8 之后,所有的类型几乎都可以自动派生 `Typeable`。 当 `biplate` 与 `partsOf` 结合时,表现出极强的威力: - **类型过滤**:在包含不同类型元素的深层嵌套元组中,`biplate` 可以单凭类型签名将其中所有的 `String` 或所有的 `Int` 抽出来拼成列表。 - **全结构大写化**:遍历结构内散落各处的所有 `Char`,统一应用 `toUpper`。 - **深层目标结构重组**:使用 `biplate` 匹配出结构中分布在不同深度的全部 `String`,结合 `partsOf` 将这些字符串视为列表并直接执行列表翻转(`reverse`),然后再原位放回。 - **字典序分区排序**:查找结构中所有在字母表中排在字符 `'m'` 之前的字符(`(< 'm')`),通过 `partsOf` 对它们进行逆序重排,而排在 `'m'` 之后的字符则完全停留在原地。 John 特别强调了类型标注在 `biplate` 中的核心地位。因为 `biplate` 是根据目标类型在数据结构中寻路定位的,如果不显式指定类型签名,编译器就无法推断出使用者想要操作哪个字段。从语义上看,类型声明本身已经构成了该 lens 镜头查询路径的不可分割的一部分。所有展示的幻灯片在生成 PDF 前都是跑通的真实测试代码,但他打趣道:“如果你盯着它看足够长的时间,它也会回过头来盯着你(If you stare at it long enough, it'll stare back at you)。” --- ### [40:05](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2405s) AST 处理实战探讨:从 SYB 到 Lens 演讲进入提问与补充讨论环节。有观众指出 `biplate` 在语法分析树(AST)重写中的典型价值。John 随即呼应了这一观点,并提到自己在深入学习 lens 之前所熟悉的 SYB(Scrap Your Boilerplate,消除样板代码技术)。 ```mermaid flowchart TD accTitle: AST 批量重写对比 accDescr: 展示传统 SYB 与现代 Lens biplate 在 AST 遍历处理上的演化关系 AST["复杂抽象语法树 (AST)"] subgraph SYB["传统方法 (Scrap Your Boilerplate)"] AST --> S1["泛型遍历函数 gmap / everywhere"] S1 --> S2["按类型匹配目标节点进行重写"] end subgraph LensBiplate["Lens 现代方案 (biplate)"] AST --> L1["biplate Traversal"] L1 --> L2["按类型寻址 + 任意 Lens 组合子组合操作"] end ``` 在编写编译器时,AST 往往包含数十种结构各异的构造器(Constructor),规模极其庞大。在很多阶段,开发者并不关心具体的 AST 上下文嵌套层次,只是想在全局做批量替换,例如将所有带有限定符的变量引用重命名,或者统一修改某种特殊类型的字段。 - 过去,Haskell 程序员会使用 `Scrap Your Boilerplate` 编写通用的泛型遍历函数,定位全局所有属于目标类型的数据。 - 现在,`biplate` 将这种基于类型的遍历完全纳入了 lens 体系。开发者可以把整个 AST 看作一个镜头目标,完全依靠类型来识别需要变动的值,并无缝搭配 `over`、`set`、`partsOf` 等全部 lens 生态组合子。 针对生态依赖的顾虑,John 澄清了一个广泛存在的误解:编写库并不需要强制依赖体积庞大的 `lens` 库本身。因为 Lens 在本质上就是符合特定签名的普通函数(遵循 Rank-N 类型的 Functor、Applicative 等类型类约束),开发者完全可以在零外部依赖的前提下,在自己的库中暴露标准 Lens 接口。 最后,John 回顾了跳过的一页关于 Lens 本质的幻灯片:Lens 仅仅是普通的高阶函数,Lens 之间的组合(如 `.`)就是最质朴的 Haskell 标准函数复合运算符(Function Composition)。也正因如此,Lens 能够毫不违和地嵌入模式守卫(Pattern Guards)、视图模式(View Patterns)以及模式同义词(Pattern Synonyms)中。针对初学者的恐惧,John 安慰大家不必理会外界所谓“海量操作符和恐怖类型论”的流言;即使遇到报错,IRC 的 `#haskell-lens` 频道也随时有核心专家(如 Edward Kmett 等人)提供答疑。 ### [44:01](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2641s) Lens 的运行性能与编译期内联优化 在问答环节伊始,现场观众针对 Lens 机制是否会引入额外的运行时开销提出了疑问。John Wiegley 解释道,Lens 在编译时能够享受到与普通 Haskell 函数完全一致的编译器优化。尽管在编写模版代码或通用遍历(如通过模版元编程生成样板代码)时可能会让人担心存在性能负担,但在实际生成的机器码中,这种担忧往往是不必要的。John Wiegley 明确指出:“Nearly everything in the Lens library is marked inline or inlineable so almost all of this syntax that you're seeing here just gets boiled off.”(Lens 库中的几乎所有内容都被标记为 inline 或 inlineable,因此大家在这里看到的所有语法糖几乎全都会在编译期被消除掉)。 当进一步被问及他在实际工程中切换至 Lens 后的性能表现时,John Wiegley 表示自己并未观察到明显的性能退化。他承认这在一定程度上取决于具体的使用场景和数据处理规模,但由于 GHC 激进的内联机制,大部分抽象层都在编译期间被完全抹平了。对于希望深入探究 Lens 背后严谨数学与函数式理论的开发者,他极力推荐了 Simon Peyton Jones 在 Skills Matter 上所做的主题演讲。他坦言自己当初正是通过那场演讲才彻底理解了 Lens 的精髓:“He has a presentation of lenses there which is what allowed me to get it I think it's very clear and extremely well presented.” - Lens 库对几乎所有组合子与辅助函数都添加了 `INLINE` 或 `INLINEABLE` 指语,使得 GHC 能够跨模块展开并消除函数调用开销。 - 经过 GHC 的优化流水线后,复杂的 Lens 链式调用通常会直接内联还原为底层普通记录项的模式匹配与构造。 - 理论推荐资源:Simon Peyton Jones 在 Skills Matter 举办的演讲,该讲座以清晰透彻的方式阐释了基于范畴论与高阶类型的 Lens 核心原理。 ```mermaid flowchart LR accTitle: Lens 编译期优化流程 accDescr: 展示 Lens 表达式如何经由 GHC 内联优化消除语法开销生成紧凑代码 subgraph Frontend["前端源码"] A["Lens 链式表达式与操作符"] end subgraph GHC["GHC 优化阶段"] B["INLINE / INLINEABLE 指语生效"] C["抽象层次消除 (Boiled Off)"] B --> C end subgraph Backend["目标代码"] D["纯普通函数与直接字段访问"] end A --> B C --> D ``` --- ### [45:10](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=2710s) 依赖权衡:库开发者与应用开发者的选型分歧 现场随后探讨了 Haskell 社区中 Lens 实现的多样性,包括基于双函子(Profunctor)的透镜模型、传统的 Getter/Setter 元组表示,以及各类针对不同应用场景裁剪出的轻量级衍生库。针对庞大的 `lens` 主包(常被称为 Kmett 风格的 lens),社区中涌现出了 `microlens`、`picolens` 和 `lens-family-core` 等轻量替代方案。John Wiegley 说明道,主包 `lens` 近年来其实已经在逐步精简依赖树,例如剥离出了单独的 `lens-action` 与 `lens-aeson`,并移除了对 `split` 库的直接依赖,整体体积已有明显改善。 针对不同类型的项目如何选择 Lens 依赖,John Wiegley 给出了非常务实而直接的建议。他强调,如果开发者的目标是编写一个供全 Haskell 社区引用的通用开源库,那么应该深入理解 Lens 理论,将透镜直接写成最基础的泛型函数形式,从而避免让下游用户强制绑定庞大的包依赖;但若是开发私有的业务应用,则完全无需为此纠结:“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 putting lenses your dependency.”(如果你正在编写一个旨在供整个 Haskell 世界引用的库,那就去学习其理论并将你的 lens 写成纯函数;但如果你只是在写一个应用程序,那谁在乎呢,直接把 lenses 作为你的依赖即可)。现场有观众随即追问:既然 Lens 在编译期都会被内联抹平,为什么还会影响依赖?John 解释称,GHC 在编译阶段必须实际加载 Lens 库的类型与函数定义才能完成代码内联与类型推导,因此编译期的依赖是无法绕过的。在简短解答了最后的细节提问后,John Wiegley 向全场观众致谢,结束了本次演讲。 - **替代方案概览**:除了完整的 `lens` 库,Haskell 生态中还并存着 `microlens`(极简依赖)、`picolens` 及 `lens-family-core` 等多套兼容类似语法的轻量化库。 - **依赖拆分历史**:`lens` 主包通过拆分 `lens-action`、`lens-aeson` 等子包,并剥离对 `split` 库的依赖,大幅降低了不必要的传递依赖开销。 - **选型原则**: - **通用公共库**:推荐直接采用纯 Van Laarhoven 形式的函数签名定义透镜,无需直接依赖完整的 `lens` 包。 - **业务应用程序**:直接引入全功能的 `lens` 包即可享受最完善的生态与操作符支持,无需过早优化构建依赖。 ## Notable quotes > [04:00](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=240s) 一旦你开始为数据类型建立起一套透镜词汇体系,你就可以随意构建任意复杂的链条;在脑海中你只需思考‘我该如何遍历它’,而不是‘我该如何层层拆包打包以维持计算正确性’。 — John Wiegley > [26:22](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=1582s) 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. — John Wiegley > [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 putting lenses your dependency. — John Wiegley > [04:00](https://www.youtube.com/watch?v=QZy4Yml3LTY&t=240s) 记录结构(Records)可能是使用 Lens 最无趣的方式,但它却总是作为 Lens 教程的入门招牌来演示。 — John Wiegley ## Mentioned John Wiegley, BAE Systems, Edward Kmett, Simon Peyton Jones, Bay Area Haskell, BayHac, Haskell, GHC, Control.Lens, TemplateHaskell, lens-aeson, lens-action, microlens, picolens, lens-family-core, thyme, Skills Matter, Scrap Your Boilerplate ## 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)

Source quotes

[4:00] John Wiegley: 一旦你开始为数据类型建立起一套透镜词汇体系,你就可以随意构建任意复杂的链条;在脑海中你只需思考‘我该如何遍历它’,而不是‘我该如何层层拆包打包以维持计算正确性’。 [26:22] John Wiegley: 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. [45:10] John Wiegley: 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 putting lenses your dependency. [4:00] John Wiegley: 记录结构(Records)可能是使用 Lens 最无趣的方式,但它却总是作为 Lens 教程的入门招牌来演示。

Source quotes

[45:10] John Wiegley: 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. [40:05] John Wiegley: 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. [20:32] John Wiegley: 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.

Original transcript

[0:00] responsible hello everybody [0:03] come to hear about lenses [0:07] um this talk will not assume any prior [0:09] experience or understanding of lenses [0:12] there will be no Theory today I will not [0:15] delve into lens types and most of the [0:17] operators I won't touch either [0:19] I work at BAE Systems my name is John [0:22] Wigley we do research there and we use [0:24] Haskell for a lot of the projects we do [0:25] and in every single Hospital project we [0:28] have there we use lenses and not because [0:30] there's that one guy who thinks every [0:32] project needs to have lenses in it but [0:34] because all of us on the team have found [0:36] they're extremely useful [0:38] um so I kind of want to show you how we [0:40] use lenses that work not all the things [0:42] that this can do [0:44] you can learn a lot of that in from [0:46] other blog posts but there's sort of a [0:47] core set of killer apps for lens that it [0:50] does better than almost any other [0:51] approach I want you to see that part of [0:53] life [0:55] so this is what I'm going to cover if I [0:57] have all the time so lenses and prisons [0:59] just to get some Basics out of the way [1:01] traversals when the power of lens starts [1:04] really kicking in [1:05] um and then map and state which those [1:08] are the two killer ones those are the [1:09] ones where once you combine lenses [1:11] prisms maps and state you've already [1:13] used lens 2 very very good effects a lot [1:16] of the other things that it can do which [1:18] I'll cover if I have time are totally [1:20] optional [1:21] um there are some of those we don't even [1:22] do but just those four are what we [1:25] mainly use it for where I am [1:29] um [1:30] so a lens as opposed to the other [1:33] constructions addresses some part of a [1:36] structure that always is there so if you [1:39] have some sort of product type so it's a [1:41] collection of multiple things [1:43] the thing in first position of that [1:45] collection is always present so the lens [1:47] lets you look at that thing or reach or [1:50] set that thing I have structure and [1:53] quotes here because it doesn't [1:55] necessarily have to be an in-memory [1:57] structure like you're used to thinking [1:58] of a real collection it can be something [2:00] computational so if I have the time in [2:03] seconds for example the hour is a part [2:06] of that structure there is a [2:07] computational structure two time that [2:10] you can turn that seconds value into [2:12] hours minute seconds get the hour out [2:14] put in a different hour go back to hours [2:16] minutes and seconds and lenses will let [2:18] you basically see that hour inside the [2:20] seconds out and it does all the [2:22] conversion for you transparently behind [2:24] the scenes [2:25] um so the very very basic most basic [2:28] kind of a lens is the one that looks [2:29] into tuples [2:31] um and and it's fairly used fairly often [2:33] but it's also overloaded in that any [2:35] product D thing has the successor these [2:38] under bar one under bar two so you're [2:40] going to see them used in other contexts [2:41] the operator here is view the most [2:44] commonly used one is just this little [2:46] carrot dot that says given the product [2:49] thing this is the piece of that product [2:51] I want so in this case I have a tuple of [2:53] three [2:54] and I want to see the second element [2:57] that operator is also just called view [2:59] as a function name and I could be doing [3:01] it this way that I have on the bottom [3:02] too [3:09] um there are cases where you want to use [3:11] the function version for example you can [3:13] use a lens in a view pattern so if you [3:16] get an argument and you want a view [3:17] pattern to pick out the the second [3:19] element of a product and pattern match [3:21] on it then you would just say view under [3:22] bar two arrow and then give your pattern [3:25] if I want to set an element inside of a [3:28] product so I have my same product of [3:30] three I use this Ampersand which is just [3:32] flipped function application so that I [3:34] can put the value on the left hand side [3:35] under bar two and then dot squiggly and [3:38] the value I want to substitute the [3:40] second element for that's called set so [3:43] I could do it as a function as well [3:44] so those two viewing set are your bread [3:47] and butter [3:50] yeah [3:51] yeah you get very used to the syntax [3:53] um I only use like 10 operators on [3:56] average so those are the ten you're [3:57] going to see in this talk but few [3:58] instead of the most used [4:00] um records are probably the least [4:03] interesting way to use lens but they're [4:05] the ones that always get presented as [4:07] here's what lenses can do it gives you a [4:09] more principled way or a more convenient [4:11] way to access and set elements of [4:14] records and if you're using records in [4:16] your program only shallowly which means [4:18] your records only ever contain [4:19] relatively fundamental types not records [4:21] of records records records [4:23] using lens in your application is going [4:25] to feel like you're doing a lot of work [4:26] for not much gear and I've had [4:28] applications where I tried to use lens [4:29] everywhere just because lens and then I [4:32] was like well I could just be using [4:33] record Syntax for this so records in a [4:35] way are sort of distinguished products [4:37] they're just collections but where every [4:38] field has a name and the way you set up [4:42] a record to be used with lenses is you [4:45] turn on template hassle you import lens [4:47] you have your record and you put an [4:48] underbar in front of every each field [4:50] name that's sort of a convention and [4:52] then you have this compile type function [4:54] make lenses and that will just make all [4:56] the lenses for your record and they will [4:57] they will be named without the underbar [4:59] so field one will get you into that [5:01] first field field two will get you into [5:03] the other field [5:06] um and here's view on a record so now [5:08] instead of the underbar one in in the [5:10] anonymous product now I have a [5:11] distinguished product I'm getting the [5:12] first field uh and I can do it with the [5:14] set and have the same principle idea [5:28] yeah yeah The Binding is very low on [5:30] ampersand [5:33] so record lenses become really useful [5:36] when structure gets deep and this [5:38] example I'm going to give you is just [5:40] record structure like records referring [5:41] to record referring to records when you [5:43] get to the day when you have a record [5:45] with a Mac whose values are lists of [5:47] other records then you will find the [5:49] true power of lens [5:51] so here is a nested record accessor this [5:54] is record of record record where I'm [5:56] reaching in to the bottom uh record and [5:59] I'm adding one to what's in that field [6:02] I could have also done other ways [6:04] there's a whole set of mathematical [6:05] operators that end with squiggle for [6:07] doing different mathematical operations [6:08] on the numerical thing that's there I'm [6:10] just going to increment by one here it [6:12] is in pure Haskell [6:13] because I don't know what the rest of [6:16] the record contains I have to preserve [6:18] it so I have to unwrap every record [6:21] add one to the element that I care about [6:23] and then repackage it so that I did so [6:25] that I keep all the other stuff that was [6:27] there whereas with lens it's doing all [6:29] that plumbing automatically for me [6:31] and lenses have this beautiful [6:33] compositionality where once you start [6:35] building up vocabularies uh lenses to [6:37] talk about your records talk about your [6:39] different data types you can just start [6:40] building arbitrarily complex chains and [6:43] in your mind you can just think how am I [6:45] traversing through this not how am I [6:47] unwrapping and re-wrapping everything to [6:49] preserve computability [6:55] what kind of operator you want if you [6:57] wanted to add [7:00] oh [7:01] yeah [7:02] yeah I mean yeah some other function [7:04] that you would have I mean it disease [7:06] just the value here you can apply [7:07] whatever you want to it [7:15] oh you would write percent tildev to [7:17] apply an arbitrary function [7:20] and I'll have percent told us in a later [7:22] example [7:26] if you have [7:27] one of them he said I want to we're [7:31] gonna get that [7:34] yeah that's coming up so this is just [7:36] lenses which guarantee you that the [7:39] element is always there so lenses are [7:41] kind of they have a they have that hard [7:43] requirement [7:45] um [7:45] if you wanted to write lenses by hand [7:47] don't try to parse all that's in the [7:48] slide I kind of wrote These slides to [7:50] also be future reference for you because [7:51] cargo quilting is a good way to get [7:53] started with lenses [7:54] there will be days when you want to [7:55] write a lens that you can't figure out [7:57] how to write it by composing other [7:58] lenses you can just write them as [8:00] playing functions and the pattern is you [8:02] get a function that is sort of an [8:05] applicative arrow and your object that [8:07] you want to modify and you've got to [8:08] descend into that structure apply the [8:10] applicative arrow sequence out the [8:12] applicative effects back to the top and [8:13] you're done if that doesn't make sense [8:15] that's okay most of the time you won't [8:17] have to write customers [8:19] here's the common operators that I use [8:22] when using lenses so the viewing set [8:24] we've already covered if the thing [8:25] you're setting to is a maybe value and [8:27] you always find yourself wanting to set [8:29] to set it to say just 10 there's a [8:31] special operator that just gives you the [8:32] just there the question mark fill [8:35] um plus there's plus tilt minus tilt [8:37] star tilt star star tilt every [8:38] mathematical operator has its tilt [8:40] variant the monoi Tilt is very very [8:43] handy uh for for adding things onto a [8:46] onto a list that's inside of a structure [8:48] percent tilt applies an arbitrary [8:50] function to the element and percent [8:52] percent tilt applies an arbitrary [8:54] applicative effect affect having [8:57] function so and then the applicative [9:00] effect is going to propagate out to the [9:01] outside [9:03] um prisms so prisons [9:06] have a have a relationship with lenses [9:08] where if lenses are looking into the [9:11] elements of products prisms are looking [9:13] at the possibilities of co-products so [9:16] if I have a tuple and I want to look at [9:18] the first element I look at a lens if I [9:20] have an either and I want to look at the [9:22] left value if it's there then I'm using [9:24] a prism so prisms give you a way to look [9:27] into things that may not exist [9:29] and either is sort of the canonical [9:31] canonical prism to you to use your own [9:35] prisms you use them on your adts so you [9:37] write your ADT as you normally would no [9:39] special naming convention here and you [9:41] call this template function make prisms [9:45] don't know the answer to that haven't [9:46] used it on [9:48] um [9:49] the convention with prisons is they [9:51] begin with an underbark when you use [9:53] them so this will this will create [9:55] prisms named under bar Alpha under bar [9:57] beta under bar gamma and then because [9:59] Alpha isn't as an undistinguished [10:01] product it'll it'll give you that as if [10:04] it were a tuple there that ends so we'll [10:07] see that we'll see how we can access the [10:08] fields of the alpha Constructor [10:11] so here's how I can view the element of [10:14] the second element of an alpha you use a [10:17] different operator instead of carrot [10:18] dots you use carrot question mark [10:20] because it may not be there [10:21] if it is there then you get just that [10:24] value and if it's not there as I have in [10:26] the second example I have a gamma there [10:27] you just get back now [10:30] um what's cooler is when you try to set [10:31] it if I want to set the second field of [10:35] an alpha to two in the first example it [10:36] does just that in the second if I try to [10:39] set a field and it's not that thing it [10:41] just ignores it it's a no op [10:45] this can be really nice if I want to [10:47] write a generic algorithm that works [10:49] over some structure that only works for [10:51] a certain class of that structure if the [10:52] structure has a particular shape I can [10:54] write it in terms of lenses and not care [10:56] and then the user who gives it to me [10:57] I'll do the right thing if it's the [10:59] value I want otherwise [11:02] error in that case [11:03] to cause an error on trying to set it if [11:05] it's not that thing haven't done that [11:19] well it is a hackathon you should try it [11:21] see what happens [11:24] there's the IX in the app yeah we're [11:27] getting into that so the next uh oh and [11:29] so I wanted to show you the with and [11:31] without lens so this is the width lens [11:32] of doing these the incrementing of one [11:34] inside of a prism this is the without so [11:37] I have to case on the original data type [11:39] figure out that's the thing that I that [11:41] my prism applies to then I have to [11:43] repackage it with having broken down the [11:46] record that represents the uh the [11:48] argument to Beta and then applying it to [11:50] the correct field so in this case I'm [11:52] mixing what would have been a lens and [11:54] what is a I mean what it would have been [11:56] a prism and what would have been a lens [11:57] and you can see that as the composition [12:00] gets larger this gets crazy uh there are [12:03] actual cases where I had a record that [12:05] had maps that had records and when I [12:07] wrote the Haskell of it just to prove to [12:08] my co-worker that lenses were expressive [12:10] it was literally 20 lines of Haskell [12:12] code doing nothing except with the one [12:14] line of lens code is doing [12:16] but that's the power [12:18] um the power to effort ratio the effort [12:21] you're putting into lens goes you get [12:23] more bang for your buck the more you're [12:25] composing lessons the more you're going [12:27] deeply the more you're dealing with [12:28] computational structure [12:30] um [12:36] try to use the prism operator on [12:39] something which is [12:41] you get crazy typos yeah because all of [12:45] this is being done with type classes so [12:47] as you compose these things you're [12:49] basically at you're basically [12:50] aggregating a constraint where it takes [12:52] on the most uh specific constraints [12:54] needed to suppress Express the operation [12:56] so if you do it and apply the wrong [12:58] operator you're going to get oh I don't [12:59] have this type of instance available and [13:02] those type errors unfortunately it's not [13:04] always clear what it is that you did [13:06] wrong [13:11] you know that you did something [13:14] um this is how you would write a prism [13:15] by hand it's a little easier you just [13:17] say how to go from the the right type up [13:21] into the other type and then how to [13:22] break down that type to get to the part [13:26] so a traversal like prisms that [13:30] reference a thing that may or may not [13:31] exist traversals reference many things [13:34] that may or may not exist for example [13:36] every element of a list [13:39] um traversals [13:41] are very very cool very powerful and in [13:43] some cases where you think you're using [13:45] a prism in lens you might actually be [13:46] using a traversal there's a little bit [13:48] of conceptual overlap there but if we [13:50] got into the theory that's where the [13:52] differences [13:54] you may think of business as just a [13:56] special case [13:58] uh so here is preview [14:00] um preview is the name of that till [14:02] question mark operator [14:03] IX is [14:06] a traversal here it looks like it's a [14:08] prism but it's a traversal that's [14:10] looking at the one the first element of [14:12] the list with index zero so that'll be [14:14] the second element here [14:15] um and it's telling me that that second [14:17] element is just two because it exists if [14:20] it didn't exist it would even do nothing [14:21] and then the same in setting works just [14:23] like a prism I say I want to set the [14:25] second element in the list if it had [14:27] been a shorter list it would have just [14:28] done nothing [14:36] because a traversal can reference a lot [14:38] of things I Oh you mean y i x is yes [14:41] um if you if you see shock off here ask [14:43] him that if you know the answer that [14:45] sort of like the Yoda of lens [14:49] um I don't know the exact answer [14:52] um so I wrote a a traversal called [14:54] digits that just takes a number and [14:56] turns it into a list of numbers [14:58] representing every digit so that I could [15:00] say what is the what is the third number [15:03] from PI uh it's just four [15:06] much information here and then I can [15:09] send the third digit inside so this is [15:11] giving me another example of how [15:12] traversal can be a computation as well [15:13] and let me address everything inside of [15:16] a structure that I map onto a notion of [15:19] a list of summit of entities [15:22] and then they're also flexible if I set [15:24] the third digit to 99 it just makes the [15:25] number bigger so that I can stop that [15:27] 99. [15:30] it could be [15:33] I'm not saying that I mean there are [15:35] there are lenses in the world that are [15:36] not entirely principled but are still [15:37] candy but yeah you can write a version [15:41] of this that will give you an error if [15:42] you try to put something in there that's [15:43] too big the way that I'm doing this one [15:45] is I'm literally just taking this number [15:47] uh doing the show cutting it up [15:51] doing read on all those and then [15:53] converting that operation [15:56] but I think that that breaks for example [15:57] if it would if it were the list of zero [15:59] and zero [16:02] ES [16:08] yeah and I tried to write that generally [16:10] like the python splice operator [16:13] so I and I got it to work but it didn't [16:15] fulfill the laws so very likely this one [16:17] doesn't have ability [16:20] um so monoids monoids uh are related to [16:23] traversals in that if I view a traversal [16:25] so I have a traversal that say looks at [16:27] a whole bunch of elements in a structure [16:29] and I just still did the arrow dot uh [16:31] the the carrot Dot [16:33] the reason why I mentioned this is [16:34] because you're going to do it and you're [16:36] going to get an error about how [16:37] something isn't a monoi you're going to [16:39] wonder what the heck do monos have to do [16:40] with anything [16:41] what the traversal will do is if you [16:44] view a traversal it views the monoid [16:46] combination of everything the traversal [16:49] points at and that's sort of like the [16:50] default Behavior [16:52] carrot dot dot is usually what you want [16:55] if you want to view an entire traversal [16:58] what carrot dot dot does is it turns [17:00] every element that you look at into a [17:02] Singleton list so that the monoi can [17:03] group them all together into a list so [17:05] here's an example using the monoid so [17:08] Traverse says address every element in [17:11] the collection [17:12] and then I'm just going to say 2 which [17:14] is the sort of trivial getter that lists [17:17] any function into into a getter [17:20] it's going to take every element of this [17:22] list and it's going to pass it to some [17:24] to create some of that element and then [17:26] because I'm using carrot dots it's just [17:28] going to monoially append all the [17:29] results and give me the sum and then [17:31] below you see carrot dot dot just lets [17:33] me use Traverse to get this into a list [17:35] now this isn't very interesting I've [17:37] used I've gone from a list to a list but [17:39] it does get more interesting when you're [17:41] traversing more complex things [17:45] um a fold is like a traversal except [17:47] it's kind of uh it's renal you can't [17:50] push back into it there are tons of [17:52] folds so here all of is a fold that [17:55] takes a that takes a traversal and then [17:57] applies a function to every element of [18:00] that traversal and sees whether they're [18:02] altered [18:03] this is a full returning function so [18:05] what I'm saying here is [18:07] look at every Tuple in this list and [18:10] look at its second element are they all [18:11] even [18:13] of course you could write this using [18:15] regular old Haskell functions and [18:17] composition you could where this becomes [18:19] more useful is when you start working [18:21] with lenses and you have a lens and [18:23] you're like oh I have this lens I don't [18:25] want to figure out how to translate that [18:27] into the regular Haskell version now I [18:29] can apply this fold with it is [18:33] yeah so same Traverse [18:36] now that's part of the magic of the [18:38] theory of how these lenses work not [18:40] covered [18:43] watch it come out if you want your mind [18:45] blown [18:47] um [18:54] you could do it that way too you could [18:56] you could Traverse and say um to all [18:59] and then look at the mono [19:01] and whether it's implemented this way or [19:03] not [19:04] there are lots of folds so you have a [19:07] lot of options for things you can do [19:09] with your traversals besides just look [19:10] at them with you [19:12] uh and there's still more of them so in [19:15] terms it probably the largest collection [19:18] of things I found in the lens library is [19:19] folds there's a very large collection [19:21] like this of traversals too uh it's just [19:24] a very very big vocabulary for doing a [19:26] lot of the things you're used to doing [19:27] normal high school but now you can use [19:29] your lenses in [19:30] conjunction with those operations [19:32] so here's a little review of the [19:34] vocabulary because if you look at the [19:36] Habit uh the haddock lens you're going [19:38] to see a lot of words in the type [19:39] signatures [19:41] um we saw a lens already that's the [19:42] thing that I can read and write to that [19:44] addresses a single element there's also [19:46] a getter which is read only for a single [19:48] element so it's got fewer restrictions [19:50] on it more things can be Getters and can [19:51] be lenses so we can use two to lift any [19:54] functions together [19:56] you have isos which is like a lens [19:58] except the thing that you're lensing to [20:00] can be let can go back to the thing that [20:02] you lens that you look at it from so it [20:05] means that it's an alternate [20:06] representation for the same thing [20:08] um [20:09] sorry what's the intuition [20:12] it's an isomorphism okay yeah so you've [20:14] got [20:15] um so the example here lazy you've got a [20:17] strict version of a byte string and a [20:19] lazy version of a byte string there is a [20:21] ISO called lazy that you can in your [20:23] lens composition you'll just say blah [20:25] blah blah dot lazy dot blah blah blah [20:27] and now you've turned the strict byte [20:28] string in the midst of your traversal to [20:30] release [20:32] um then you see the prism maybe you can [20:34] read from it maybe you can write from it [20:36] maybe it accesses zero or one elements [20:38] full Setters interpersonals are working [20:41] on multiple elements so zero or more [20:44] um and these are common operators to be [20:46] using with traversals so character is [20:48] very very common carrot question mark [20:50] probably most common uh question mark [20:53] bang says I know it's there give me an [20:56] error if it's not [20:57] all right there are times when it's [20:58] convenient when you can reason about [21:00] your code and know that it's going to [21:01] exist [21:03] um so maps maps is kind of [21:05] where I get on the train with lens so [21:07] maps are what really pushed me into lens [21:10] because the things you can do with maps [21:11] means you don't have to remember all the [21:13] functions [21:14] so at is your [21:17] um is your accessor if we're looking at [21:19] the element of a lens where it returns a [21:21] maybe [21:22] um so you say carrot dot at one if it's [21:26] there you can adjust if it's not you go [21:27] to nothing it's a little different from [21:28] my x i x being the traversal that will [21:31] let you look at things that are in a map [21:32] we're going to look at in a moment the [21:34] nice thing about at is that when I want [21:36] to assign to it [21:38] um if I assign to that position as just [21:42] X it will add it to them if ice is there [21:45] any difference [21:48] oh yeah I did get I forget too I forgot [21:51] to [21:51] um this was at if it was present at if [21:54] it was absent right but then oh yeah I [21:56] got it wrong yeah I wrote tests for all [21:58] of these but then the slide deck [22:00] references the test by name [22:04] that should be doing at three and having [22:07] nothing [22:09] so you so use the lens on this [22:10] presentation in which everything is [22:12] correct [22:14] um non is a nice thing to use with at uh [22:18] one of the reasons why you would use [22:19] that instead of IX to get an element hat [22:21] says uh knowing with at says if it's [22:24] there get what the app would have found [22:26] if it's not there give the argument you [22:28] pass the knot [22:29] so here I'm saying at two non-z well two [22:32] is the the value for 2 is there so [22:34] return it otherwise if we're gonna add [22:36] three non-z I will return Z so non gives [22:40] you a way to have a default if it's not [22:42] a prism or less [22:44] so [22:46] yeah no Theory today [22:51] um it makes it easy to get out of [22:52] certain questions [22:53] so here's the IX version of doing things [22:56] until uh Carey question mark ix1 it is [22:59] there X3 it's not there [23:02] um here's me demanding it with uh IX [23:05] probably not a safe thing to do and then [23:08] setting with IX [23:10] this is different than setting without [23:12] so if I say at one dot till just C it [23:18] will create it if it's not there meaning [23:19] the the set will always work and if I [23:22] set with nothing it will always delete [23:23] the element or do nothing if it wasn't [23:25] there already I X however works as a [23:28] traversal where I'm setting that element [23:30] if that key is there it will set the [23:34] value of that key if that key is not [23:35] there it does nothing [23:36] so so that's a difference between Pat [23:39] and I X I end up using both [23:42] and then just as we had non to go with [23:44] at we have failing to go with ions [23:47] failing says try the first traversal if [23:50] that doesn't do anything try the next [23:51] Traverse [23:55] oh that's just a way for me to construct [23:56] Maps that's data.map.front list [24:03] um so state state is the other cool [24:05] thing and when you've got State and maps [24:07] and Records all together [24:10] um so just as you have yet [24:12] with regular um State and monad State [24:15] you have used to apply a lens to the [24:18] thing in your state so I have a product [24:20] in my state 10 comma 20 I use underbar [24:23] one that will return tense uh you know [24:28] um uses takes a lens and a function gets [24:31] you the thing via the lens applies the [24:32] functions to it the function to it on [24:34] the way out just like [24:36] pre-use is if you're working with a [24:38] prism or traversal [24:40] and pre-uses like users that should pass [24:42] a function along with it [24:45] set is dot equals [24:48] and this is kind of cool because now you [24:50] can modify subsections of your state [24:53] writing code that begins to look a lot [24:55] like C [24:56] you can say I so so in my state is a [24:59] list of four elements I say ix1 dot [25:01] equals five I'm saying if the list is [25:03] long enough change its second element to [25:05] B5 otherwise don't do anything it's not [25:07] an error I can even use magnetic [25:10] functions and this becomes much more [25:12] useful in state t [25:14] the magnetic function and I have a [25:15] version of bind with a squiggly instead [25:17] of a straight line goes into what the [25:19] target what the traversal points at all [25:21] right [25:26] [Music] [25:28] um ix1 dot equals five is a magnetic [25:31] action that's over monad statement some [25:33] State containing a list of four elements [25:35] right yeah I was trying to find the most [25:38] representative way to write this that [25:40] would still work as part of the Kim [25:42] Suite so sorry if it's a little unclear [25:43] but yeah these are magnetic actions on [25:45] the left of exact state I'm going to [25:47] state I'm running the action number [25:50] so the ix1 squiggly arrow is just a [25:52] magnetic setting [25:54] Target of that one [25:56] traversal in prison over percent equals [25:59] lets me apply a function to the thing in [26:01] my state so it's like modified so it's [26:03] modifiable potentially a sub element [26:05] um ID the function ID is a is an ISO [26:09] because everything is as smart of itself [26:12] so if you had a state that was just a [26:14] number and you wanted to apply a [26:15] function to it you could also write ID [26:17] percent equals function but apply to [26:20] that function to the right state [26:22] now zoom zoom is there Zoom is a little [26:25] more powerful than it even looks at [26:27] looks at first because it lets you write [26:29] your stateful functions in a different [26:30] way so Zoom takes a lens [26:33] and it says and and another stateful [26:36] magnetic action and says this magnetic [26:39] action you're passing to zoom I'm going [26:41] to execute it with its state being the [26:44] sub State referenced by the lens [26:47] so my state here is a product whose [26:50] first element is another product [26:52] and I'm saying dive into the first [26:55] product and run a stateful action on [26:58] that where under bar two there is [27:00] referring to the two in my original [27:03] state and then the result of that [27:04] becomes one come for [27:06] as a product commentary [27:09] so Zoom is letting me look into parts of [27:12] my state is that always equivalent to [27:14] just composing the two lenses [27:18] um yeah but if you say you had multiple [27:20] actions that you that you had zoomed in [27:22] you'd have to be repeating that [27:23] composition everywhere [27:33] um where Zoom kind of really comes into [27:35] its own is where you have an application [27:37] you want to be really principled so you [27:39] want to do everything with staple [27:41] functions instead of using i o and iOS [27:43] or whatever but you never know when [27:45] you're going to want certain pieces of [27:46] State in different places so you end up [27:48] making everything be in the same state [27:50] product and then you have functions that [27:52] will only ever reference a part of that [27:54] state and other functions that reference [27:56] a different part well Zoom allows you to [27:58] program differently each function can [28:01] specifically address only the kind of [28:03] state it is interested in and you can [28:05] keep lifting those building blocks into [28:08] larger composed State actions using Zoom [28:11] so now you have a program where every [28:14] little piece only does what that piece [28:16] is and it what that piece knows about [28:18] you don't have to have some part of the [28:19] state but that function never needs to [28:21] care about [28:22] just make it focus on in this case like [28:24] just the first product and now I can [28:26] lift that second magnetic action into a [28:29] state where it's Product Common number I [28:32] could also lift it into a state where [28:34] under bar two meant something else I [28:36] could lift it into a state that was a [28:37] tuple of three I could lifted into a [28:39] state that was a typical of four so Zoom [28:41] is allowing my my more directed [28:43] functions to become usable in more [28:45] context [28:47] Museum [28:48] s [28:51] and then say okay [28:58] um [29:04] uh another case where State kind of kind [29:07] of comes in useful is yeah you will find [29:10] yourself especially when dealing with [29:11] records doing multiple sets in a row and [29:14] what's nice about using the Ampersand [29:15] version of this is that you can just [29:17] stack them so that it kind of begins to [29:19] look a little bit like record setting [29:20] where you have field after field after [29:22] field [29:23] but there's another way to do it you can [29:26] say Ampersand tilt and it says the thing [29:30] on the right is a magnetic action [29:33] whose state is the thing on the left [29:36] and so now I can use my DOT equals [29:38] operator to assign to all the members of [29:41] that record [29:43] this would this would be the typical way [29:44] to write it but you can write it like [29:46] this too and now you can use the other [29:48] magic of State like [29:52] so things we did not cover today [29:56] um and these are also mostly things that [29:58] I don't use either [30:00] there's a lot of cool stuff in lens um [30:02] just like there's all these cool things [30:04] for dealing with State there's there are [30:05] also cool things for dealing with uh [30:07] writer and there's a there's a kind of a [30:09] zoom equivalent for writer too [30:12] um lens action lets you deal with lenses [30:14] that have a monadic result although [30:16] that's not all that much used lens Ison [30:19] however is very much used at my company [30:21] that lets you take arbitrary values Json [30:25] values and just dive into them with all [30:27] kinds of accessors for looking at the [30:28] different [30:30] that that right there lens Ison makes it [30:33] very easy to sell uh lens inside my [30:35] company to people who haven't worked on [30:37] their projects before [30:38] it begins to look like XPath really for [30:41] dealing with anything uh time pH yme is [30:45] a cute library that lets you work with [30:46] time values as it had a whole bunch of [30:48] lenses for looking at the hours [30:51] there's a whole family of lenses called [30:54] indexed lenses that that make available [30:56] to you the index of the thing that [30:58] they're traversing on [31:00] that we haven't covered there's zippers [31:02] which let you kind of dive into a [31:04] structure get to get to the position you [31:06] want to lens to but then walk around [31:08] from there oh I want to go down to this [31:10] deep sub-object but now I want to go to [31:13] the second sub-object right next to it [31:14] and sort of iteratively crawl over there [31:17] instead of constantly going down to the [31:19] next object and the next object in the [31:21] next object [31:23] um there's exception lens exception [31:25] lenses stuff for dealing with arrays and [31:27] vectors thanks for dealing with file [31:28] paths and also a whole bunch of uh [31:30] functions for dealing with different [31:32] numerical stuff like converting to [31:34] different vertices going from hex to [31:36] octal [31:37] doing different math operations I think [31:41] um the last sort of thing I want to show [31:42] you here is a very very cool thing that [31:45] will blow your mind [31:46] um [31:47] and if you want to keep your mind just [31:49] stay away from these slides [31:52] parts of so I said a traversal lets you [31:55] look at multiple elements in the lens [31:57] now a traversal we've kind of been [32:00] boring here and they're looking at [32:01] lenses I could have a traversal for [32:02] example over all the values in a map [32:06] parts of takes a traversal and presents [32:10] it as a list [32:12] lets you modify that list the structure [32:15] of that list [32:17] and then puts it back [32:19] okay so this is what's happening in this [32:22] example [32:23] I have a list [32:25] I'm going to Traverse and I'm using [32:27] Traverse with this D at the end that [32:29] allows me to refer to the indices of the [32:31] triggers I could have used Traverse [32:32] without the D but then I couldn't use [32:34] this word indices on the right [32:35] traversed looks at every element of the [32:38] list [32:39] but I only care about the ones whose [32:40] indices are odd [32:42] positions one three five Etc I want to [32:45] reverse those elements [32:50] I mean it does [32:51] it does right it's very actually easy to [32:53] read once you can [32:56] imagine [32:59] [Music] [33:02] there are there are caveats to parts of [33:04] like [33:09] um another thing you can do with parts [33:10] of is you could I could have assigned [33:12] another list [33:13] to take all the odd positions if there [33:16] were not enough it would have only gone [33:18] up to as many as I had and would have [33:20] left the other ones unchanged if there [33:22] were too many it would have just ignored [33:23] all the extras I gave so parts of has [33:26] some little restrictions that are can be [33:28] procrastinate but it won't give you [33:29] narrative [33:31] so here's another little example [33:34] uh this time I get to use Traverse only [33:35] because I don't need to reference the [33:37] indices [33:38] I want to look at every element in this [33:41] list that is less than four [33:43] I want to I want to sort that and then [33:46] reverse the sorted list [33:49] could you imagine trying to write this [33:51] the plain task [33:53] res lens lets me state it in a [33:55] declarative way exactly what I mean all [33:58] the parts of this list that are less [33:59] than four [34:01] I want to sort them in place and reverse [34:03] them please [34:08] this slide deck is on GitHub [34:11] is there for you [34:13] along with a lot more examples than [34:14] these two in a file called lenses.hs [34:16] which is actually what I use to Cargo [34:18] code my own password [34:19] I often forget how to use something [34:22] like I know that [34:24] jaywigley github.com and it should be [34:27] the first uh repo in the list [34:31] thank you [34:32] um here's the parts of with each so say [34:34] I have a gigantic Tuple each lets me is [34:37] a traversal over every element of the [34:39] Tuple [34:40] so now with parts of I can look at that [34:42] listed I can look at that Tuple as if it [34:44] were a list and I can sort the elements [34:46] in that table [34:47] try that with that school this one I [34:50] know the function you have to write but [34:52] it wouldn't work [34:54] um setting like I said to him here I'm [34:56] going to do a traversal over all the [34:59] alphabetic characters in the string and [35:02] I am going to overwrite as many of those [35:05] characters as I have in my replacement [35:07] string [35:08] so it takes the hello world and it [35:10] writes howdy over all the alphabetic [35:12] characters [35:17] and then last example here I have [35:20] um [35:21] so I take I take this this empty sort of [35:25] a template for for a time value [35:27] and I want [35:30] to write over the minutes and seconds [35:32] part [35:33] so I'm going to say everything that is [35:35] passed position three at or past [35:38] position three that is equal to a zero [35:41] put these numbers over top [35:44] so the lens here is giving me a way to [35:46] identify the part of the template that I [35:48] want to become [35:50] and then I can just decide to repeat [35:52] now because you've all been good and we [35:54] have time [35:56] um there's one more this is even worse [35:58] this is worse than parts of it [36:00] I've not yet used this in practice but [36:04] um you can't not know about five foot [36:08] and you don't want to know how biplates [36:10] implemented either [36:11] my plate is a traversal [36:15] that [36:16] can walk through arbitrary structures [36:20] and identify things of a specific type [36:25] so this traversal says go through this [36:28] structure and I have this this ugly here [36:30] make this this uh nested Tuple here with [36:33] um [36:34] elements of different value types [36:36] elements of different types make a list [36:38] of all the strings that are in that [36:40] collection [36:41] yes [36:46] does it require a white constraints [36:48] yeah but now you get typable on [36:51] everything post GHC seven eight [36:57] um yeah [36:58] and then this is this is me getting out [37:00] every integer [37:01] all right these are these are [37:04] as because we can mix my plate in parts [37:08] of [37:10] it [37:11] so before we do that [37:14] I'm going to take this collection the [37:16] same collection all the examples use the [37:18] same collection I want a two upper every [37:20] character to send it [37:22] that's the intuition [37:24] so there's a there's a library called [37:26] uniplate [37:28] research that yeah [37:32] um so here it is with parts up so I'm [37:35] going to buy plate to get all the to [37:37] look at all the strings in the [37:39] collection I'm going to use parts of to [37:41] let me [37:43] think of those strings as a list and I'm [37:45] going to reverse that list [37:48] so I'm basically gonna so so my buy [37:50] plate is going to take all of the things [37:52] yeah [37:55] the last thing lets me know [37:58] yeah how how could you write that in [38:01] regular customers right [38:04] [Music] [38:09] thank you [38:10] this is the thing that I I don't know if [38:13] it was Ed or shotgun showed me this in [38:15] the Haskell lens Channel about system [38:18] so this is more for posterity than your [38:21] own personal use [38:23] um [38:24] and then this one says I want to buy [38:26] plate over all uh characters less than [38:30] the character m [38:31] M use parts of to sort those [38:34] so anything above m in the alphabetic [38:37] lexical ordering is going to stay in [38:39] place anything below m is going to get [38:42] reversed in position [38:46] but see it stays it stays recognized I [38:49] mean you understand what this is doing [38:50] right right you did you would not [38:51] understand what the hospital [38:54] and then this is the last one [38:59] yeah all these slides are actually tests [39:02] to run for the PDF's name [39:11] if you stare at it long enough it'll [39:13] stare back at you [39:18] so this last one says [39:21] foreign [39:22] look at everything in here [39:25] from which I can Traverse a string [39:28] I want to look at the head of that thing [39:31] and uppercase [39:34] so I get to capitalize all strings that [39:36] are in the structure [39:39] now you have to use type signatures when [39:41] you're using my plate a lot because if [39:43] you don't it has no idea which kind of [39:45] element you want to address [39:46] so the types here are actually part of [39:49] the signature of this lens in a sense [39:50] because the type is what is giving by [39:53] plate the information on what to [39:57] here the type has to be even more [39:59] general or anything that supports data [40:01] just because of the operations [40:05] so that's the end of the slides with the [40:08] exception of this latter half which is [40:09] there just for your amusement I hope I [40:12] motivated what makes lenses nice and [40:13] useful and that even though people may [40:15] tell you there's a zillion operators [40:17] there's terrible types there's there's [40:19] portable Theory you don't actually have [40:21] to know that to make productive uh use [40:23] of lens you would like to know to [40:24] understand the errors sometimes but you [40:26] will kind of get a flavor for what the [40:27] errors are and go to the Haskell lens [40:29] Channel if you run into problems there [40:31] are true experts there who will know the [40:33] answers to your questions [40:34] that you can apply lenses [40:39] in a Library without depending on the [40:41] lens right lenses are just functions [40:44] which I wasn't going to get into but I [40:47] will for now since lenses are just [40:50] functions you can put them in your [40:51] library with zero dependency on lens and [40:54] they will just work [40:55] because all these fancy lens types when [40:57] you look under the hood they're [40:59] dysfunctions with constraints like [41:00] applicative functor traversable Etc so [41:04] if you know how they translate you can [41:05] write lenses [41:13] with [41:16] you know possible sources [41:19] like just change all of the qualified [41:21] variable references into something else [41:24] and it's you just say Q Bar X [41:28] 2key [41:32] of the purse tree and just [41:35] yeah let me restate what his comment is [41:37] because there was a time that I actually [41:38] would have wanted my plate before we got [41:41] in too heavy into lens and she never [41:43] used SPD [41:44] that's why I mean scrap your boilerplate [41:47] so if you write a compiler you've got [41:49] gigantic asts and a lot of times there [41:52] will be common little bits that are all [41:54] throughout your ASD but in different [41:56] positions and you don't really care [41:58] about the structure of the thing you [42:00] just parsed you just want to say sweep [42:03] through my AST and do a variable [42:04] substitution or sweep through my AST and [42:07] do this operation on all fields for [42:09] every Constructor that have this type in [42:11] common [42:12] so in in the old days you would use [42:15] scrap your boilerplate to very easily [42:17] write a function like that a generic [42:19] traversal function that targets all your [42:21] things of Type X within a structure [42:24] biplate is that within the lens world so [42:26] I can have an AST and I can annotate [42:29] that ASD or write something that does a [42:31] substitution on particular parts of my [42:34] AST identifying what I want to mutate by [42:36] type rather than its position [42:40] and that's actually a case where I [42:42] wouldn't [42:44] there's a slide you skipped between by [42:48] plate or between my plate and parts of [42:50] or right [42:51] I mentioned this earlier I had just put [42:54] this in here to show you what it looks [42:55] like [42:56] because lenses are ordinary functions [42:59] and then this is sort of one of the key [43:01] intuitions of lens if you get if you get [43:03] somewhat into the theory of it because [43:04] they are just functions that composition [43:07] operator between lenses is just the [43:09] regular function composition operator [43:10] and you can use them in all kinds of [43:12] places because the assumptions almost [43:14] everywhere [43:15] so pattern guards view patterns [43:19] um inside your pattern synonyms anywhere [43:21] you could have applied a function to do [43:22] something you can use view or set or all [43:25] of or all these various different things [43:26] with lenses [43:42] I don't [43:45] ask chocolate [43:50] is a general functions [43:56] if you really want to but most of them [43:59] are pretty darn quick [44:01] they get all the same optimizations that [44:03] regular Haskell functions do [44:05] Yeah by play you might be paying an [44:07] overhead cost but you do with scrap [44:09] report [44:17] yeah nearly everything in the lens [44:19] library is marked inline or in lineable [44:21] so almost all of this syntax that you're [44:24] seeing here just gets boiled off [44:30] coffee [44:33] that's a good question [44:35] performance [44:38] I haven't really noticed too much for it [44:41] in switching to emergency [44:43] well depends what you're doing with them [44:44] maybe but yeah [44:47] um if you are interested in the theory I [44:49] would highly recommend the talk given by [44:51] Simon Payton Jones on skills matter he [44:53] has a presentation of Blends there which [44:55] is what allowed me to get it I think [44:57] it's very clear and extremely well [44:59] presented [45:06] that I don't know at all [45:08] yeah [45:10] any other questions yes [45:17] yes [45:18] Theory questions for Bowden [45:26] this is [45:27] not pro functor lenses not Tuple of [45:31] getter and center lines [45:35] the lens package [45:40] yeah there's micro lens Pico lens lens [45:43] family core there's a lot of different [45:45] alternate representation of this concept [45:48] lenses dropped some of its huge [45:50] dependencies it had in the past it broke [45:52] out lens action it broke out lens ice on [45:54] it took away [45:56] um a dependency on a thing [46:00] on on split on the split Library so it's [46:03] it has slimmed down a little bit but if [46:05] you're writing a library intended for [46:08] consumption by the whole classical World [46:09] learn the theory and write your lenses [46:11] as functions [46:12] if you're writing an application who [46:14] cares just putting lenses your [46:16] dependency [46:18] that's what we do [46:19] sorry didn't you just say that lens [46:21] compiled down to just [46:24] phone cannot have like [46:27] well it needs to see the lens Library [46:30] compile time to be able to have access [46:32] to the definitions [46:38] so lens library for application [46:55] about that [46:57] do you know if there's anything like [46:58] this [47:00] Association [47:02] oh reader I see um [47:09] thank you very much everybody [47:11] [Music] [47:16] foreign