The lens abstraction, and related abstractions, make the concept of a field of an abstraction, a first class notion. It is a little language of its own, uses nice type trickery, and certainly has a learning curve. But when well-understood, it allows for concice, expressive code, and opens new ways of abstraction. It is worth considering in every medium-to-large sized project that handles deep structured data. It is also worth learning because some interesting libraries, such as diagrams, make heavy use of it.

Getters and Setters

Getters

Haskell’s record syntax makes it rather easy to reach deeply inside such a data structure. For example, if we want to get the x-position of an atom, we can write

getAtomX ::Atom->Double
getAtomX = _x . _point

So the record accessors serve as getters, and if we want to reach deeply into a data structure, we can compose these getters. Of course, this is just syntactic sugar, and if we would not have used record syntax, we could easily implement _x and _point by hand.

Setters

Setting a value is not so easy. There is the record-update syntax that allows us to write the following (but again, the record-update is just syntactic sugar, and we could have written the same with regular pattern matching):

Efficiency

Unfortunately, this is not very efficient. Function over uses the lens l twice: Once to get the value, and once again to set it. And since over is used in comp, if we nest our lenses a few layers deep, this gets inefficient very quickly.

Enter the Functor

But clearly, we want to do this trick not just for IO, but for many type constructors. Some of which might not be Monads. So if we look closely at the code for overIO, we see that all we really need is a functor instance. So let us generalize this:

Lens is just a type synonym

But now Lens has become a product type with only one field. This menas that the type Lens a b is isomporphic to the type forall t. Functor t => (b -> t b) -> (a -> t a). In that case, why bother with the Lens constructor and the overF field name at all? We can get rid of them!

typeLens a b = forall t .Functor t => (b -> t b) -> (a -> t a)

Interestingly, now

comp :: Lens a b -> Lens b c -> Lens a c
comp l1 l2 = l1 . l2

so we can get rid of this function as well, and use plain old function composition .!

Traversals

Where there is a Functor, an Applicative cannot be far. What if we do change the constraint in the lens type:

Yes we can! Well, almost, the compiler wants us to provide an Applicative instance for I. Fine with me:

instanceApplicativeIwhere
pure x =MkI x
f <$> x =MkI$ (unI f) (unI x)

Since set is just defined in terms over over, we can now also relax the type signature of set to use Traversal.

Non-Lens traversals

Can we do the same thing with view? No, we cannot! The constant functor is not applicative (there is no way of implementing pure :: a -> C b a).

So a Traversal a b describes how one can (possibly effectful) set or update values of type b in a (like Lens), but not get a value of type b (unlike Lens). If we try to think of concrete a where that is the case, what come to mind?

For example Maybe b! We certainly can apply a function to the contained thing, if it is there, and maybe even with effect:

Here we cannot expect to have a useful view because a [a] might not have an a, but also because it might have many.

Getting all of them

So we cannot have view because the structure might have zero or more than one elements. Well, then at least we should be able to get a list of them?

listOf ::Traversal a b -> a -> [a]

Again, we can try to implement that using a suitable Functor. We compare the desired type with the type of a Traversal and find that we again need a constant functor, this time, though, storing a list of bs:

(In reality one would use Const [b] x here with the Applicative instance for Const with the Monoid constraint on the first argument of Const, but since we did not discuss Monoid in this class, we do it by hand here.)

What is a Traversal now?

Similar to Lens is one position in a data structure (and precisely one, and one that is always there), Traversal describes many position in a data structure.

And since Lens and Traversal compose so nicely, you can describe pretty complex “pointer” well. For example with xml-lens, this Traversal extracts the title of all books with a specific category from an XML fragment.

Further reading

The story presented here is rather simple. If you look at the lens libray you see more abstractions (Prism, Iso, etc.). This library also comes with a large number of concrete lenses, traversals etc for many data structures, and has cool tricks so that _2 for example is a lens for the second element of a tuple, for any tupel size.

In that package, what we called Lens and Traversal is actually called Lens' and Traversal', and the version without quote allows over to change the type of the thing pointed at.

But note that even in the lens library, all these notions are just type synonyms, so you can define lenses as we did, without using a library, and you are still compatible with these libraries! Also see microlens for a library, compatible with lens, but smaller, less dependencies and better documented.