Roman Cheplyaka

Lens is unidiomatic Haskell

Edward Kmett writes:

Ironically if I had to say anything from the traffic in my inbox and on #haskell about it, it is mostly the old guard who gets disgruntled by lens.

So let me try and explain why that is. I’ll go ahead and say this: lens is unidiomatic Haskell.

By which I mean that lens isn’t like any other library that we normally use. It doesn’t follow the conventions of Haskell APIs, on which I elaborate below.

Now let me clarify that this doesn’t necessarily mean that lens is a bad library. It’s an unusual library. It’s almost a separate language, with its own idioms, embedded in Haskell.

It is as unusual as, say, Yesod is. But unlike Yesod, which follows Haskell’s spirit, not letter (syntax), lens, I argue, follows Haskell’s letter, but not its spirit.

So here’s why I think lens violates the spirit of Haskell:

  1. In Haskell, types provide a pretty good explanation of what a function does. Good luck deciphering lens types.

    Here’s a random function signature I picked from lens:

    below :: Traversable f => APrism' s a -> Prism' (f s) (f a)

    Despite having some (fairly basic) understanding of what prisms are, this signature tells me nothing at all, even despite the usage of type synonyms.

    So you have to rely on documentation much more than on types. Yeah, just like in Ruby.

  2. Usually, types in Haskell are rigid. This leads to a distinctive style of composing programs: look at the types and see what fits where. This is impossible with lens, which takes overloading to the level mainstream Haskell probably hasn’t seen before.

    We have to learn the new language of the lens combinators and how to compose them, instead of enjoying our knowledge of how to compose Haskell functions. Formally, lens types are Haskell function types, but while with ordinary Haskell functions you immediately see from types whether they can be composed, with lens functions this is very hard in practice.

  3. The size of the lens API is comparable to the size of what I’d call «core Haskell» (i.e. more or less the base library). It is also similar in spirit to base: it has a big number of trivial combinations of basic functions, in order to create a base vocabulary in which bigger programs are expressed.

    Ordinary libraries, instead, give only basic functions/combinators, and rely on the vocabulary provided by base (or lens) to compose them together.

    This is why I regard lens as a language in its own. And this demonstrates why learning lens is hard: surely learning a new language is harder than learning a new library.

  4. Dependencies. A library as basic in its purpose as lens ideally should have almost no dependencies at all. Instead, other libraries should depend on it and implement its interface. (Or even do it without depending on it, as is possible with lens.)

    A package implementing lenses that depends on a JSON parsing library and a gzip compression library sounds almost like a joke to me.

    OTOH, it kind of makes sense if you think about lens as a language. It just ships with a “rich standard library”. Nice!

  5. Backward composition of lenses. It’s a minor issue, and I wouldn’t mention it if it wasn’t a great demonstration of how lens goes against the conventions of Haskell.

Note that I’m not trying to make a judgment here (although my tone probably does give away my attitude towards lens). I’m simply explaining why people may dislike and resist it.

Nor am I trying to argue against any particular design decision of lens. I’m sure they all have valid rationale behind them.

I just hope that someone will write an idiomatic Haskell library as powerful as (or close to) lens, with perhaps a different set of compromises made. Otherwise, I’m afraid we all will have to learn this new language sooner or later.