wiki

Prism

also: prisms, affine traversal

The 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.

A lens always finds its focus, because every record has every field. A variant value is one constructor out of several, so looking inside a particular constructor can fail, and the getter returns an option. In exchange, a prism can go backwards: the constructor contents are enough to build the whole value.

A prism as a record: preview matches, review builds.

type ('s, 'a) prism = { preview : 's -> 'a option; review : 'a -> 's }
type shape = Circle of float | Rect of float * float
let circle =
{ preview = (function Circle r -> Some r | _ -> None);
review = (fun r -> Circle r) }
let over p f s = match p.preview s with Some a -> p.review (f a) | None -> s

Compiled with ocamlopt 5.5.1.

circle.preview (Circle 2.) -> Some 2
circle.preview (Rect (1., 2.)) -> None
circle.review 3. -> Circle 3
over circle (fun r -> r *. 2.) (Circle 2.) -> Circle 4
over circle (fun r -> r *. 2.) (Rect (1., 2.)) -> Rect (1, 2)

The laws: preview (review a) = Some a, and if preview s = Some a then review a = s. The last line above is the useful property in practice: a modification through a prism that does not match leaves the value alone, so a deep edit into JSON or an AST is safe when the path does not exist.

see also

referenced by

further reading

  • M. Pickering, J. Gibbons, N. Wu, “Profunctor optics: modular data accessors”, The Art, Science, and Engineering of Programming 1 (2017).