Introduction to optics: lenses and prisms

Optics are a very useful tool in functional programming. They can reduce the amount of code we have to write significantly, as well as making operations clearer and more readable. We’ll talk about two main types: Lens and Prism. Both propose a way for “getting” and “setting” values in a data type. Lens is for working with product types (e.g. tuples, objects), Prism is for working with sum types (e.g. maybe types, union types) and arrays.

The problem

This tutorial assumes that our data structures are immutable. To see why we might want to consider optics, we’ll look at a simple example. We’ll define two object types

Given an instance of Address, getting the street name is quite simple

Setting it to a new value is less so

As we can see working with values deep within the data structure starts to get awkward.

Lenses

A lens is a first-class reference to a subpart of some data type. Given a lens there are essentially three things you might want to do

  • view the subpart
  • modify the whole by changing the subpart
  • combine this lens with another lens to look even deeper

A lens is nothing more than a pair of functions, a getter and a setter. The type S represents the whole, A the subpart

Let’s define a lens for the type Address with focus on the street field

Now let’s define a lens for the type Street with focus on the name field

Is there a way to get a lens for the type Address with focus on the inner name field?

Composition

The great thing about lenses is that they compose

Now handling the inner name is trivial

Note that our lenses can be composed of any number of other lenses.

Modify

Let’s say we need to set the first character of the address street name in upper case. Mapping a function over a part of a data structure given a lens is as simple as get the value, apply the function to the value, set the new value to be the result

Prisms

In the above example, we used capitalize to upper case the first letter of a string. It works but it would be clearer if we could use Lens to zoom into the first character of a string. However, we cannot write such a Lens because a Lens defines how to focus from an object S into a mandatory object A and in our case, the first character of a string is optional as a string might be empty. For this we need a sort of partial Lens.

Prisms work with cases like this. A simple way to put it is that a prism is like a lens, but to something which is or is not there.

Note: sometimes this kind of optics is named Optional.

As you can see lenses and prisms are very similar, except that the getter in the prism returns a maybe type. Let’s write a prism for the first letter of a string

Similarly to composeLens we can compose prisms obtaining again a prism

All Lens can be seen as Prism where the optional element to zoom to is always present

Union types

Prisms are applicable to all union types. How would we write a prism for a custom union type of our own?

Here’s a prism to the address of Domicile when the case is office

We now have (in the combination of lenses and prisms) some powerful, general and predicatable tools with which to work on data.

mathematician and rock climber

mathematician and rock climber