Introduction to the Lens Library

What problem is being solved?

Haskell has record types.

data Foo = Foo { bar :: Int, baz :: Baz }
data Baz = Baz { oof :: String, off :: () }

Construction is easy.

foo :: Foo
foo = Foo { bar = 3, baz = Baz { oof = "Oof", off = () } }

Access is easy, "getters" are functions.

oof ( baz foo )  ==>  "Oof"

Updates? There is no update, because Haskell data is persistent, instead you construct a copy of the record that has different values for attributes. There is even a notation for it,

foo { bar = 4 }

But this looks ugly as soon as we "update" an attribute that's nested.

foo { baz = (baz foo) { oof = "Up" } }

Note that we have to copy the expression "foo" here. Deeper nesting => more ugly.

How does the solution look, from the outside?

For the two updates discussed above:

foo & ( bar .~ 4 )
foo & baz . oof .~ "Up"

Here, both "bar" and "baz . oof" are lenses, and ".~" constructs (or extracts) a setter from a lens.

Note: "&" is just flipped application, "a & f = f a", so the above is equivalent to

( bar .~ 4 ) foo
( baz . oof .~ "Up" ) foo

From a lens, we can also get a view:

foo ^. bar
foo ^. (baz . oof)

Note the Pascal-like syntax (uparrow and dot).

We could write the second example as

foo ^.baz ^.oof

but these would be two views. The point is that lenses are composable (as functions - the dot "." is really the Haskell dot that composes functions).

What is the price to pay for this?

Install the lens library (50 modules, with a lot of external dependencies, some of which you will not understand, and, most likely, never use).

Then rewrite your record types as follows:

{-# language TemplateHaskell #-}

import Control.Lens

data Foo = Foo { _bar :: Int, _baz :: Baz }    
data Baz = Baz { _oof :: String, _off :: () }  

$(makeLenses ''Foo)
$(makeLenses ''Baz)

Relative updates

Another typical usage is to modify an attribute by applying a function

  ==> Foo {_bar = 3, _baz = Baz {_oof = "Oof", _off = ()}}

foo & bar %~ succ
  ==> Foo {_bar = 4, _baz = Baz {_oof = "Oof", _off = ()}}

foo & baz . oof %~ reverse
  ==> Foo {_bar = 3, _baz = Baz {_oof = "foO", _off = ()}}

Why the name?

A lens provides a view of part of an object, and it allows "change" the object transforming the change in the part to a change in the whole.

Imagine you are a watchmaker. You repair a watch. You use a magnifying glass (a lens) that shows just the part inside the watch that you are indeed investigating, or modifying.

What if I don't have this problem in the first place?

Even if you don't need record updates - Lens is a general concept, and there are lenses for many many types besides records.

That's the reason for the large number of dependencies: the lens package includes lenses for many "outside" types (not in Prelude). And it is also one reason why it attracts some criticism

We look, in particular, at collections (tuples, lists, sets, maps). For tuples, there are lenses focused to the components, called "1, 2, ..."

("foo", "bar") ^. _1          ==> "foo"
("foo", "bar") & _1 .~ "up"   ==> ("up","bar")

For lists, "ix i" is a lens that focuses the i-th element

 [[1,2,3],[4,5,6],[7,8,9::Integer]] ^?!  ( ix 2  . ix 1 )  ==>  8

The question mark indicates that the element might not be there, the bang indicates that we are quite sure it is (and risk an exception otherwise)

The nice thing is this:

[[1,2,3],[4,5,6],[7,8,9::Integer]] &  ix 2  . ix 1 .~ 0

  ==> [[1,2,3],[4,5,6],[7,0,9]]

This also works for maps:

import qualified Data.Map as M
M.fromList [("foo",1), ("bar", 3)] & ix "foo" %~ succ
  ==> fromList [("bar",3),("foo",2)]

How does it work, from the inside?

A lens is a function that transforms something that happens (type "f" in the following) to the thing that is focused (type "a") to the full object (type "s"). The general type is

type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t

For simple lenses,

  • f is the identity functor
  • a = b = the type of the thing at focus
  • s = t = the type of the full object

For example, the lens (setter) that focuses the first component of a pair, is (in the identity monad)

_1 = \ f ->  \ (x,y) -> (f x, y)

The setter implementation is

l .~ v = \ o -> l (const v) o

You can check that this works as advertised above: start an blank ghci (do not load Control.Lens), and enter the definitions of "_1", ".~", and "&" as shown, then

("foo", "bar") & ( _1 .~ "up" )

where parentheses are needed because we did not declare operator precedences. Try composing your home-made lenses as well

(("foo", "bar"), 0) & ( ( _1 . _1 ) .~ "up" )

There - you just started your own lens library.

What more can lenses do for me?

Much more. Some topics not covered here:

  • lenses with other functors (in the above, we always were in the identity functor)
  • traversals (access several elements "at the same time", well, at least, with writing code just once)
  • prisms (to look left or right - if your data type has several constructors)

See the documentation, of which there is plenty. Perhaps this is a good starter.