Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
What are Lenses?
Lenses are composable functional references. They allow you to access and modify data potentially very deep within a structure!
Ignoring the implementation for the moment, lenses provide us with two operations:
view :: Lens' a b -> a -> b set :: 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 :: Lens' (a,b) a _2 :: 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 Prelude's
(.), which specializes perfectly:
(.) :: Lens' a b -> Lens' b c -> Lens' a c
(.) 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" >>> set (_2._2) "leaders" ("hello",("world","!!!")) ("hello",("world","leaders"))
Note: If you have no background in imperative programming and would prefer to see the lenses composing in the "expected" order, you can use the
(>>>) function from
Control.Arrow and then the ordering is back to normal
Finally, you can use the ordinary Prelude
id as the identity lens
id :: Lens' a a
which just gives you back the value when used with
view 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 -> Lens' a b -> b (.~) :: Lens' a b -> b -> a -> a
With these you can now use lenses like field accessors.
> ("hello",("world","!!!"))^._2._1 "world" > ("hello",((1,True,"world"),"!!!"))^._2._1._3 "world"
You can also write to them 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 => Lens' a b -> b -> m () use :: MonadState a m => Lens' 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.