After seeing this comment on reddit asking if lens solves the “overloading issue” with Haskell’s record systems, I thought I’d write up what I found here. By the way, The answer is yes, it can solve the problem with makeFields. The code for this blog post is available as a github gist.

Often, when making lenses I’ll use makeLenses. This is one of the simplest Template Haskell lens functions, and tries to make a named lens for each field in your record. If you try to avoid the clashing field names by doing something like this:

… you’ll end up with many different (and long) names for everything. That’s not nice.

Instead you can use makeFields. This will give you a typeclass for each field called (for example) HasOne.

If we’d used makeFields in our example above, both Foo and Bar would be instances of HasOne, we could refer to the fields using the name one, and everything would be just rosy. A more complete example is below. I’ve used 2D and 3D vectors (Vec2 and Vec3) to demonstrate how we can create lenses named x, y, and z super-simply just using makeFields.

Here, makeFields uses some default rules to strip away the underscore and record type name to transform our field names (_vec3X, vec2X) into the lens name (x). These rules can be customised by using makeFieldsWith.

We can use ghci to examine the created typeclasses (using :t HasX at the GHCI prompt):

Aside from the strange type variable names, this is fairly simple: the HasX class has a single value, x, which is the Lens into our record. The type parameters to HasX are the record type, then the field type. That means we can write a normal Vec2 like this.

v :: (v ~Vec2, HasX v Double) => v
v =Vec21010

Note that the type could be written simply as v :: Vec21. Our more complicated type signature is just intended to illustrate how the type parameters get ‘filled in’. The tilde (~) in a type signature denotes equality.

A more useful example, in which we want to write a function that zeroes out the x component of any vector, is below:

The Control.Lens.TH module is full of useful functions like makeFields, so I definitely recommend reading through the documentation to see if there’s anything useful to you in there.

This isn’t strictly true, because we’re also asserting that Vec2 must be an instance of HasX v Double. If we removed the makeFields call, this line would complain that there was no instance, whereas if we’d written the type simply as v :: Vec2, it would compile.↩