Lens
also: lenses, functional reference, optic
A first-class pair of a getter and a setter for one part of a structure, with laws that make them agree: you get back what you set, setting what you got changes nothing, and a second set overwrites the first. Lenses compose, so a path through nested records is one value.
A record update in OCaml 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, and composing two lenses gives the path through both.
A lens as a record of two functions, and composition.
type ('s, 'a) lens = { get : 's -> 'a; set : 'a -> 's -> 's }let ( |-- ) l1 l2 ={ get = (fun s -> l2.get (l1.get s));set = (fun a s -> l1.set (l2.set a (l1.get s)) s) }type address = { city : string; street : string }type person = { name : string; address : address }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 }) }
With ada living on St James's Sq, London. Compiled with ocamlopt 5.5.1.
(address |-- city).get ada -> "London"((address |-- city).set "Paris" ada).address.city -> "Paris"((address |-- city).set "Paris" ada).address.street -> "St James's Sq"
The laws are what make a lens more than two functions that happen to share a type: get (set a s) = a, set (get s) s = s, and set b (set a s) = set b s. They are what let a lens be used without reading its definition.
Haskell's lens library encodes a lens as a function polymorphic over a functor, forall f. Functor f => (a -> f a) -> s -> f s, which makes composition ordinary function composition. That needs higher-kinded polymorphism, which OCaml has only through functors, so a record of functions is the usual OCaml representation.
see also
- PrismThe counterpart of a lens for sum types: a partial getter that succeeds only on one constructor, and a builder that makes a whole value from that constructor's contents. A lens composed with a prism has at most one focus and no builder, which is called an affine traversal.
- Higher-kinded typeA type that abstracts over a type constructor rather than over a type, so that Functor f can be instantiated at Maybe or at lists without naming their element types. It is what makes a single Monad class usable across every container instead of one class per shape.
- Value restrictionThe rule that only syntactic values have their type variables generalized in a let. An application such as List.map f is not a value, so its type variables stay weak, fixed by the first use. OCaml relaxes the rule for type variables that occur only covariantly.
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
- 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 TOPLAS 29 (2007).
- T. van Laarhoven, “CPS based functional references” (2009).
- M. Pickering, J. Gibbons, N. Wu, “Profunctor optics: modular data accessors”, The Art, Science, and Engineering of Programming 1 (2017).