At Trello we’ve been playing with lenses, from the world of modern functional programming. Lenses are five stars but seem very much like abstract foo-foo at first.

Thesis, which I will attempt to exponentiate without anything’s resembling category theory or otherwise abstract foo-foo, which I love and believe is critical for fighting entropy in large systems but [and I agree] is pedagogically troublesome: Lenses shine when dealing with complicated data structures, like JSON.

Example

> import Control.Lens
> import Data.Aeson
> import Data.Aeson.Lens
> import Data.Aeson.Types
> import Data.Text

> let json = replace "'" "\"" "{'user': {'boards': [{'title': 'hello, world', 'rating': 1.11111111111111111111111111111111111}]}}"

> json ^? key "oops"
  Nothing

> json ^? key "user"
  Just (Object (fromList [("boards",~snip~)]))

> json ^? key "user" . key "boards" . nth 0 . key "title" . _String
  Just "hello, world"

> let firstUserBoard = key "user" . key "boards" . nth 0

> json ^? firstUserBoard . key "title" . _Number
  Nothing

> let firstUserBoardRating = firstUserBoard . key "rating"

> json ^? firstUserBoardRating . _Double
  Just 1.1111111111111112

> json ^? firstUserBoardRating . _Number
  Just 1.11111111111111111111111111111111111

> putStrLn . unpack $ json & firstUserBoardRating . _Double %~ (fromIntegral . floor)
  {"user":{"boards":[{"rating":1,"date":"20150211","title":"hello, world"}]}}

Apologies for the long lines. And Trello doesn’t really let you rate boards, but shush.

Unpacking the example

We use JSON all the time! So when we see something like lenses, our eyes go wide. With very little code we have:

  • Error handling. The ^? operator ensures our program never crashes. If at any point in our lens pipeline we cannot find the right key into an object or the right index into an array, we will get a Nothing back. And if the server incorrectly returns a string where we expected a number, we will get a Nothing back. The downside is, of course, that we have to unwrap a Just value. Not too onerous, with Haskell’s syntax.

  • Speed. What is the one thing a developer values more than safety? Performance! Lenses are written by some of the best Haskell authors out there, and — for all the abstract foo-foo — this code flies like lightning. It helps also that GHC is a cutting-edge speed devil of a compiler, bar none.

  • Composability. Lenses are nouns not verbs, which means we can name firstUserBoard and firstUserBoardRating like I did above, and then use them in other lenses. Because they are nouns we can give them names, which not only clarify our long pipeline by turning code into documentation, but also allow us to pass them around to other functions, if we so chose.

  • Faithfulness. Have you ever used a JSON library that encoded numbers with IEEE floats? I have, and while it is rare to want the arbitrary-precision numbers guaranteed by the spec, when you do want it you really do want it. Lenses provides _Number as the faithful representation of a JSON number, and _Double for when you just want to do some quick math fast.

    We can even write more complicated lenses that venture outside the spec, like _Date. It would be quite easy and short.

    Types matter. Most bugs in Trello trace back to our using strings, a one-dimensional list of characters, instead of The Right Thing™. Computers may not, but humans need more dimensions.

    We joke a lot that it would be a full-time position, and quite satisfying at that, to simply dictate the type signatures in our code. Call it a Type Czar, whose job is to reject pull requests that added functions with types too dumb.

  • Unification of getters and setters. There is a Berlin Wall in programming, and it’s between getters and setters. The idea that you have to write this boilerplate over and over again is outdated in 2015:

     public String foo {
       get { return this._foo; }
       set { this._foo = value; }
     }
    

    Why did I have to write the this._foo twice? Why is value a special keyword I have to use; why is it not a function parameter? Why do we have properties when we have methods already? Don’t even get me started on @synthesize foo = _foo; in Apple C.

    These annoyances add up. I feel old. I feel old. I am weary of object-oriented programming, for boilerplate like this. For having to tell my IDE to generate code for me. The ghosts of functional programming — of LISP and of macros and of code-as-data-as-code — reach out to me, to taunt me; and rightly so. And I do not know what I have become.

    With lenses, a getter and a setter is one value, and it’s a value familiar to any functional programmer: a function. firstUserBoard is a function, as surprising as that may seem. Functions compose. Functions are nouns. Functions are in my lifeblood in a way that “properties” as defined by section god-knows-what of god-knows-where in the official Microsoft Oracle Design-By-Committee Corruption-Of-Smalltalk♮ spec.

The way I think about lenses is as pipelines, rushing data forwards (pulling a smaller structure out of a larger one) and backwards (updating a smaller structure inside a larger one, and being able to wrap everything back up), connected at the joints by our function composition operator ..

Lenses make me excited about programming again.

Lenses are imperfect

  • Studying the structure of lenses requires rank-n polymorphism and existentially-qualified typeclasses. Can we build a type system one day where lenses feel natural, that better encodes the nature of lenses' twisty function-lifting than arrows can? I do not know. I would dedicate the rest of my life to doing so, if I knew how.

  • As a result, the type inferencing for lenses is weak at times. You will get alerts about Monoids when you really just need to use ^? instead of ^.. The reason is fascinating, but nobody should need to research the math of an error in order to understand the error.

  • Lenses use cute names and operators because there were no names for the ideas lenses put forth. All words are made up, as the old joke goes. Hopefully one day we will all share a vocabulary for these beautiful visitors from outer space. But until then, vocabulary is part of the learning curve.

But and so

History will look back kindly on lenses, even if it remains underground and punk rock like it is today.

If you want to play with the code:

#!/bin/sh
set -eu
mkdir /tmp/lens-sandbox
cd !$
cabal sandbox init
cabal install lens lens-aeson -j
cabal repl