Lens
A record update in a language with immutable data is shallow. Changing the city inside the address inside a person means rebuilding every record on the way down by hand. A lens packages one step of the way down, as a getter and a setter, and composing two lenses gives the path through both[1][3]. Three laws make the pair behave like a field, even when the part is computed rather than stored. Lenses were introduced by Foster, Pierce and colleagues in 2005 as bidirectional transformations for the view-update problem of databases[1][4], and became a standard tool of functional programming through van Laarhoven's encoding of 2009[2] and Kmett's Haskell lens library[6]. A prism is the corresponding optic for sum types, and profunctor optics give a uniform account of the whole family[3].
Example
A lens as a record of two functions, with composition, for a person with a nested address.
(* A lens focuses on one part 'a of a structure 's. *)type ('s, 'a) lens = { get : 's -> 'a; set : 'a -> 's -> 's }(* Composition: focus with l1, then with l2 inside that. *)let ( |-- ) l1 l2 ={ get = (fun s -> l2.get (l1.get s)); set = (fun a s -> l1.set (l2.set a (l1.get s)) s) }let over l f s = l.set (f (l.get s)) stype address = { city : string; street : string }type person = { name : string; address : address; born : int }let address = { get = (fun p -> p.address); set = (fun a p -> { p with address = a }) }let city = { get = (fun a -> a.city); set = (fun c a -> { a with city = c }) }let street = { get = (fun a -> a.street); set = (fun s a -> { a with street = s }) }let born = { get = (fun p -> p.born); set = (fun b p -> { p with born = b }) }
Reading, setting and modifying through a composed lens.
open Lens
Running it.
LondonSt James's Sq, Parisunchanged: St James's Sq, LondonST JAMES'S SQ
The original value is unchanged: set builds a new person with a new address, sharing the parts that did not change.
Laws
A lens from to is a pair and satisfying[1]:
The laws are what make a lens more than two functions that happen to share a type. They say that splits into the focus and a complement that leaves alone, so a lens can be used without reading its definition. They are preserved by composition. A lens that satisfies the first two is called well-behaved, and one that satisfies all three very well-behaved[1].
A lens need not correspond to a stored field. A virtual field computed from others is a lens if setting it adjusts the underlying data consistently, and a lens that changes anything besides its focus breaks set-get.
A computed lens, and one that does too much, checked against the laws on random values.
open Lens
Running it.
address |-- city get-set 0 set-get 0 set-set 0 failures in 1000age get-set 0 set-get 0 set-set 0 failures in 1000address |-- sloppy get-set 0 set-get 505 set-set 0 failures in 1000
The sloppy lens trims the street whenever it sets the city, so setting the city to its current value is not a no-op; the failure shows up only when a random street has surrounding spaces.
Van Laarhoven lenses
Van Laarhoven observed that a lens can be encoded as a single function, polymorphic over a functor[2]:
The function takes an action on the focus and lifts it to the whole structure. Choosing the functor chooses the operation: the constant functor turns it into a getter, the identity functor into a modifier. Composition is ordinary function composition, and a lens library needs no lens type at all, only type synonyms; a lens written this way depends only on the base library. The van Laarhoven lens article treats the encoding in detail.
Van Laarhoven lenses in Haskell, without a library.
{-# LANGUAGE RankNTypes #-}import Data.Functor.Const (Const (..))import Data.Functor.Identity (Identity (..))-- A van Laarhoven lens: one function, polymorphic in the functor.type Lens s a = forall f. Functor f => (a -> f a) -> s -> f slens :: (s -> a) -> (a -> s -> s) -> Lens s alens getter setter k s = fmap (\a -> setter a s) (k (getter s))-- Choosing the functor chooses the operation.view :: Lens s a -> s -> aview l s = getConst (l Const s)over :: Lens s a -> (a -> a) -> s -> sover l f s = runIdentity (l (Identity . f) s)set :: Lens s a -> a -> s -> sset l a = over l (const a)data Address = Address { city :: String, street :: String } deriving Showdata Person = Person { name :: String, address :: Address } deriving ShowaddressL :: Lens Person AddressaddressL = lens address (\a p -> p { address = a })cityL :: Lens Address StringcityL = lens city (\c a -> a { city = c })-- Composition is ordinary function composition.main :: IO ()main = dolet ada = Person "Ada" (Address "London" "St James's Sq")putStrLn (view (addressL . cityL) ada)print (set (addressL . cityL) "Paris" ada)-- Any functor works: a list of results gives every variant of an edit.print (map (city . address) ((addressL . cityL) (\c -> [c, c ++ "!"]) ada))
Running it.
LondonPerson {name = "Ada", address = Address {city = "Paris", street = "St James's Sq"}}["London","London!"]
The last line uses the list functor, which the getter–setter pair cannot express: it applies a nondeterministic edit to the focus and returns every resulting structure. Replacing Functor by Applicative gives traversals, which focus on any number of parts[7]. The encoding needs higher-kinded polymorphism, which OCaml has only through module functors, so OCaml libraries usually represent lenses as records of functions.
Theory
A lens is equivalently a function , returning the focus together with a way to rebuild the whole from a new focus. This is a coalgebra of the store comonad , and the lens laws are exactly the laws of a comonad coalgebra[5]. Lenses are also the case of optics for the product: a lens is a way of writing for some hidden complement . Replacing the product by a sum gives prisms, and profunctor optics describe lenses, prisms, traversals and the rest uniformly, as functions polymorphic over profunctors with the appropriate structure[3][8].
History
The view-update problem, of translating an update to a database view back to the underlying tables, was studied by Bancilhon and Spyratos in 1981[4]. Foster, Greenwald, Moore, Pierce and Schmitt introduced lenses as bidirectional tree transformations for the Harmony synchronizer in 2005, with the laws in their present form[1]. Functional references appeared in several Haskell libraries in the following years. Van Laarhoven described the functor encoding in 2009[2], O'Connor related it to traversals[7], and Kmett's lens library of 2012 built a hierarchy of optics on it[6]. Pickering, Gibbons and Wu gave the profunctor account in 2017[3], and Riley a categorical one in 2018[8].
see also
- PrismA prism is the counterpart of a lens for sum types: a partial getter, preview, that succeeds only on one case, and a builder, review, that makes a whole value from that case's contents. Two laws make them agree. Prisms compose with each other, and a lens composed with a prism gives an affine traversal, which has at most one focus and no builder. They make deep edits into data like JSON or syntax trees safe when the path does not exist, and they are one of the standard optics, alongside lenses, isos and traversals.
- Van Laarhoven lensA van Laarhoven lens is a lens represented as a single function polymorphic over functors: forall f. Functor f => (a -> f a) -> s -> f s. Instantiating the functor gives the operations: the constant functor yields the getter and the identity functor the setter. Lenses in this form compose with ordinary function composition, and replacing Functor with Applicative gives traversals, which compose with lenses the same way. The representation is the basis of Haskell's lens library.
- FunctorIn functional programming, a functor is a type constructor f with an operation fmap : (a -> b) -> f a -> f b that applies a function inside the structure without changing its shape, preserving identity and composition. Lists, options, trees and functions out of a fixed type are functors. The notion comes from category theory. In OCaml the word also names parametrised modules such as Map.Make.
- Higher-kinded typeA higher-kinded type is a type constructor, such as Maybe or List, that takes types as arguments, and higher-kinded polymorphism is the ability to abstract over such constructors with type variables, so that Functor f can be instantiated at Maybe or at lists without naming their element types. Kinds classify types the way types classify values: Int has kind Type, Maybe has kind Type → Type. Higher-kinded variables are what make a single Functor or Monad class usable across every container. Haskell and Scala support them directly; OCaml expresses the same abstraction with module functors or an encoding, and Java and Go cannot express it.
- TraversableTraversable is the abstraction of a container that can be rebuilt while an applicative effect is performed on each element in order: traverse : (a -> f b) -> t a -> f (t b). Validating every element, parsing each string, numbering the nodes of a tree and taking cartesian products are all traversals with different applicative functors. traverse subsumes both map (with the identity functor) and foldMap (with a constant functor into a monoid), and it satisfies laws saying it visits every element exactly once.
- Yoneda lemmaFor a functor F and an object a, natural transformations from Hom(a, -) to F correspond one to one with elements of F a. In Haskell, forall b. (a -> b) -> f b is isomorphic to f a, and the left-hand form turns a chain of fmaps into one composed function and a single fmap.
- ComonadA comonad is the categorical dual of a monad: a functor w with extract : w a → a, which reads the value in focus, and extend : (w a → b) → w a → w b, which applies a function that looks at a whole context to every position of that context. Where a monad describes producing a value with an effect, a comonad describes computing a value from a context, such as a cell from its neighbourhood, a value from its history, or a field from a whole structure.
- Value restrictionThe value restriction is the rule, in ML-family languages, that a let-bound expression has its type variables generalized only if it is a syntactic value, such as a function, a constant or a constructor applied to values. An application such as List.map f or ref [] is not a value, so its type variables stay weak and are fixed by their first use. Without the restriction, a reference could be given a polymorphic type and used to store a value at one type and read it at another. OCaml relaxes the rule for type variables that occur only covariantly.
- ZipperA zipper represents a data structure together with a focus, a position inside it: the substructure at the focus, and the path from the focus back to the root together with everything to either side of that path. Moving the focus one step and editing at the focus take constant time, and the structure is persistent, so edits share everything off the path. The type of paths is the derivative of the structure's type, in the sense of calculus.
referenced by
- lens in HaskellBuilding a lens from first principles, then the real library: Lens, Prism, Traversal, Iso, Fold, indexed optics, and generic derivation.
- Lenses and Prisms in OCamlAn optics library whose kinds are a phantom polymorphic variant, and a ppx that derives lenses from records, prisms from variants, and both from field paths and patterns.
further reading
- [1]J. N. Foster, M. B. Greenwald, J. T. Moore, B. C. Pierce, A. Schmitt, “Combinators for bidirectional tree transformations: a linguistic approach to the view-update problem”, ACM Transactions on Programming Languages and Systems 29 (2007).
- [2]T. van Laarhoven, “CPS based functional references”, blog post (2009).
- [3]M. Pickering, J. Gibbons, N. Wu, “Profunctor optics: modular data accessors”, The Art, Science, and Engineering of Programming 1 (2017).
- [4]F. Bancilhon, N. Spyratos, “Update semantics of relational views”, ACM Transactions on Database Systems 6 (1981).
- [5]J. Gibbons, M. Johnson, “Relating algebraic and coalgebraic descriptions of lenses”, Electronic Communications of the EASST 49 (2012).
- [6]E. Kmett, the lens library, Hackage (2012).
- [7]R. O’Connor, “Functor is to lens as applicative is to biplate: introducing multiplate”, arXiv:1103.2841 (2011).
- [8]M. Riley, “Categories of optics”, arXiv:1809.00738 (2018).
last updated