Haskell Language Lens


Lens is a library for Haskell that provides lenses, isomorphisms, folds, traversals, getters and setters, which exposes a uniform interface for querying and manipulating arbitrary structures, not unlike Java's accessor and mutator concepts.


What is a Lens?

Lenses (and other optics) allow us to separate describing how we want to access some data from what we want to do with it. It is important to distinguish between the abstract notion of a lens and the concrete implementation. Understanding abstractly makes programming with lens much easier in the long run. There are many isomorphic representations of lenses so for this discussion we will avoid any concrete implementation discussion and instead give a high-level overview of the concepts.


An important concept in understanding abstractly is the notion of focusing. Important optics focus on a specific part of a larger data structure without forgetting about the larger context. For example, the lens _1 focuses on the first element of a tuple but doesn't forget about what was in the second field.

Once we have focus, we can then talk about which operations we are allowed to perform with a lens. Given a Lens s a which when given a datatype of type s focuses on a specific a, we can either

  1. Extract the a by forgetting about the additional context or
  2. Replace the a by providing a new value

These correspond to the well-known get and set operations which are usually used to characterise a lens.

Other Optics

We can talk about other optics in a similar fashion.

OpticFocuses on...
LensOne part of a product
PrismOne part of a sum
TraversalZero or more parts of a data structure

Each optic focuses in a different way, as such, depending on which type of optic we have we can perform different operations.


What's more, we can compose any of the two optics we have so-far discussed in order to specify complex data accesses. The four types of optics we have discussed form a lattice, the result of composing two optics together is their upper bound.

enter image description here

For example, if we compose together a lens and a prism, we get a traversal. The reason for this is that by their (vertical) composition, we first focus on one part of a product and then on one part of a sum. The result being an optic which focuses on precisely zero or one parts of our data which is a special case of a traversal. (This is also sometimes called an affine traversal).

In Haskell

The reason for the popularity in Haskell is that there is a very succinct representation of optics. All optics are just functions of a certain form which can be composed together using function composition. This leads to a very light-weight embedding which makes it easy to integrate optics into your programs. Further to this, due to the particulars of the encoding, function composition also automatically computes the upper bound of two optics we compose. This means that we can reuse the same combinators for different optics without explicit casting.