Skip to content
ekmett edited this page Aug 9, 2012 · 52 revisions

What are Lenses?

Lenses are composable functional references.

Ignoring the implementation for the moment, lenses provide us with two operations:

view :: Simple Lens a b -> a -> b
set :: Simple Lens a b -> b -> a -> a

So we can view a lens as a pair of a getter and a setter that are in some sense compatible.

We'll use the following lenses to start off:

_1 :: Simple Lens (a,b) a
_2 :: Simple Lens (a,b) b

to both read from

>>> view _2 ("hello","world")
("world")

and write to parts of a whole:

>>> set _2 42 ("hello",0)
("hello",42)

Moreover, we can compose lenses with (.).

(.) :: Simple Lens a b -> Simple Lens b c -> Simple Lens a c

Notice (.) composes in the opposite order from what you would expect as a functional programmer, but to an imperative programmer they provide the nice idiom that

>>> view (_2._1) ("hello",("world","!!!"))
"world"

Finally, you can use id as the identity lens

id :: Simple Lens a a

which just gives you back the value when used with (^.) and which when set completely replaces the old value.

They satisfy 3 common-sense laws:

First, that if you put something, you can get it back out

view l (set l b a) = b

Second that getting and then setting doesn't change the answer

set l (view l a) a = a

And third, putting twice is the same as putting once, or rather, that the second put wins.

set l b1 (set l b2 a) = set l b1 a

Note, that the type system isn't sufficient to check these laws for you, so you need to ensure them yourself no matter what lens implementation you use. (Some others will compose with (.) the other way.)

We define infix operators to make working with lenses feel more imperative:

(^.) :: a -> Simple Lens a b -> b
(.~) :: Simple Lens a b -> b -> a -> a

With these you can now use lenses like field accessors.

> ("hello",("world","!!!"))^._2._1
"world"

You can also write to then in something approaching an imperative style:

> _2 .~ 42 $ ("hello",0)
("hello",42)

There are also combinators for manipulating parts of the current state for a State monad, such as:

(.=) :: MonadState a m => SimpleLens a b -> b -> m ()
use  :: MonadState a m => SimpleLens a b -> m b

Using these (and other combinators for manipulating state) yields code like the following snippet from the pong example included in the distribution (where p is in the surrounding scope):

check paddle other
    | y >= p^.paddle - paddleHeight/2 && y <= p^.paddle + paddleHeight/2 = do
        ballSpeed._x   %= negate
        ballSpeed._y   += 3*(y - p^.paddle) -- add english
        ballSpeed.both *= speedIncrease
    | otherwise = do
      score.other += 1
      reset

More information about how the types for lenses can be derived is available under Derivation.

For now, you may want to explore some code that uses lenses heavily, such as the Pong Example.

Clone this wiki locally