Deciphering lens error messages, part 1

Griffin Smith
3 min readApr 25, 2018

Haskell’s venerable lens library is, in my opinion, one of its selling points as a language. It makes it possible to access, modify, and query deeply nested data structures with an efficiency that blows right pass OOP-style foo.bar.baz = “qux” dotted property access and straight into programming nirvana.

Learning to use lens can be a bit of an uphill battle, though, — while there are lots of great introductory tutorials out there (1, 2, 3), and the library’s author even gave a spectacular (and spectacularly accessible) talk on the library, all of these resources focus on what to do when everything is working well. That’s fine but, especially as a beginner, programming tends to consist much more frequently of things going wrong. Lens in particular makes this much more difficult to deal with, since due to the technique it uses to make everything compose nicely the error messages GHC gives tend to leave something to be desired. This post is the first in an ongoing series about deciphering and decoding the error messages that GHC gives us when lenses are used incorrectly.

That lens doesn’t work here!

To get started, let’s go with probably the simplest example — what happens when we try to use a lens on a data type it doesn’t work with?

For example, let’s say we have the following code:

Because we’ve used the makeFields template haskell macros for defining our lenses on the Galaxy and Human data types (rather than makeLenses or defining them manually as top-level lenses) this actually looks pretty nice. If we try to access the species field on a Human, for example, we get this:

That’s because the makeFields macro defines type classes for the lenses it defines, of the form HasThing - ie, for our Name field (which is shared by both Human and Galaxy), we’d get something like:

The two parameters to the HasName typeclass allow different data types to have a name field with different types, which is nice.

What if we don’t want typeclasses for our lens fields, however? Or what if we (very reasonably) don’t want to use template haskell, and instead write all the lenses by hand? For example, the nickname field of the Animal type is generated by makeLenses rather than makeFields, which gives us something like:

If we now try to use the nickname field on a human, we get the following, more confusing error message:

While the first line of the error message is pretty clear, the clarity goes downhill pretty quick — what’s a Getting? And why does OrganismName appear before the Animal in the function after it?

Without getting into too much detail (I’d recommend reading the lens derivation page or even the blog post that originally invented the way lenses currently work if you’re more interested), this is due to the way that the lens library uses a trick with functors to allow the kind of composition we tend to expect from the lens library. It encodes (monomorphic) lenses as:

and then swaps out the f functor to get the getter/setter behavior - Const gives us a Getter, and Identity gives us a setter.

So when we see the following function in our error message:

What GHC is actually saying is that we supplied a Getter from Animal to OrganismName! In general, any time you see the following type in an error message:

you can surmise that GHC is talking about a Getter from source to target ! And any time you see Getting target source target , you can read it as “Getting a target from a source “.

Wrapping up

I hope this post has helped start to break into understanding what’s going on under the hood of the lens library, specifically with an eye for beginning to understand exactly what to do when GHC gives an error message. In the next post, I plan to move from talking about individual instances of data structures to how to deal with collections of data structures.

Come talk to us if working with lenses and Haskell in general gets you as excited as it does me and the other engineers here at Urbint.

--

--