-
Notifications
You must be signed in to change notification settings - Fork 275
Tutorial
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.