2. WHAT?
Lenses (as a concept) are a way to focus and access
deeply into a complex structure.
"poking around inside things"
"access" here means read, write, modify, fold, traverse,
etc
lens is a package which provides an implementation of
the concept of lenses or functional references.
Availble through cabal: cabal install lens
There are many other lens packages but this is the one
we're going to look at today
We will only cover a subset in this session
Lens is a vast topic and needs a lot of time to
understand and use
Basic usage can be learnt quickly and the payoff is
worth it
3. WHY?
Convenience - will see later with an example
lens forms a combinator library and provides
composition, multiplicity, etc.
We'll see some of these behaviours here
It also provides a number of generalizations of lenses
including Prism, Traversal, Iso and Fold.
Can be used on many data types.
We'll see a few here such as record syntax, lists, etc
5. SOMEBASICIDEASOFNOTE
A lens is a first class value that can be passed around like
any other data structure
lenses compose. We've seen in the example code where
we drill two levels down in a data structure through
compositon
The fact that they compose is very powerful and can
give rise to succint code that is more understandable
This is akin to 'dotted' notation in OO languages
although we're not quite modifying state
Some of the basic uses of lenses do not need the use of
this library
The lens library, of course, provides a lot of value
addition
So, how might we create a lens without using this library?
6. HOWMIGHTICREATEA
LENS
We saw an example of a simple usage of lens
In order to better understand lenses, let's try reinvent a
small part of the lens library
8. STARTINGOUT
For this purpose, we'll treat a Lens as a combination of a
getter/setter
data NaiveLens s a = NaiveLens { getL :: s -> a,
setL :: a -> s -> s}
-- view function to focus on a particular part of the data structure
view :: NaiveLens s a -> s -> a
-- set function to set a particular part of the data structure
set :: NaiveLens s a -> a -> s -> s
-- composing two lenses
:{
let composeNaiveLenses :: NaiveLens s1 s2 -> NaiveLens s2 a -> NaiveLens s1 a
composeNaiveLenses (NaiveLens getter1 setter1) (NaiveLens getter2 setter2)
= NaiveLens (getter2 . getter1) (a s -> setter1 (setter2 a (getter1 s)) s)
:}
9. USAGE
type TimeStamp = Integer
data FrameHeader = FrameHeader {
_frameNum :: FrameNumber,
_ts :: TimeStamp
} deriving Show
type FramePayload = String
data Frame = Frame {_header :: FrameHeader, _payload :: FramePayload} deriving Show
-- lens for each field
fheader :: NaiveLens Frame FrameHeader
ftimestamp :: NaiveLens FrameHeader TimeStamp
-- composed 'timestamp' lens
ftimestamp :: NaiveLens Frame TimeStamp
ftimestamp = fheader `composeNaiveLenses` ftimestamp
10. EXTENDING
What if we want to update a value instead of get or set?
We could implement it in terms of a get and then a set,
but that would be inefficient
We could add a function for update as part of the lens
definition!
data NaiveLens s a = NaiveLens { getL :: s -> a,
setL :: a -> s -> s,
updateL :: (a -> a) -> s -> s}
updateL :: NaiveLens s a -> (a -> a) -> s -> s
11. What if the function (a -> a) can fail, or need to do some
IO to get the new value?
We could then update NaiveLens with:
data NaiveLens s a = NaiveLens { getL :: s -> a,
setL :: a -> s -> s,
updateL :: (a -> a) -> s -> s
updateM :: (a -> Maybe a) -> s -> Maybe s
updateL :: (a -> IO a) -> s -> IO s}
12. Or, even generalize:
data NaiveLens s a = NaiveLens { getL :: s -> a,
setL :: a -> s -> s,
updateL :: (a -> a) -> s -> s
updateF :: Functor f => (a -> f a) -> s
-> f s}
14. EVENMOREGENERALIZATION
What if, we could turn this:
into this:
Note: I've not understood yet why we need the forall here
as the type compile even without it
data NaiveLens s a = NaiveLens { getL :: s -> a,
setL :: a -> s -> s,
updateL :: (a -> a) -> s -> s
updateF :: Functor f => (a -> f a) -> s -> f s}
:set -XRankNTypes -- forall needs 'RankNTypes' language extension
type BetterLens s a = forall f . Functor f => (a -> f a) -> s -> f s
15. HOWDOESBETTERLENSWORK?
How is the type alias BetterLens even able to do the
things NaiveLens was doing?
What we need is a function like this for a setter
:{
-- Simply convert BetterLens to something that has the type of setL
let set :: BetterLens s a -> (a -> s -> s)
set lns a s = undefined
:}
16. type BetterLens s a = forall f . Functor f => (a -> f a) -> s -> f s
By selecting functor for f above,Identity
:{
let set :: BetterLens s a -> (a -> s -> s)
set lns a s = runIdentity $ -- To unwrap the Identity functor.
-- Note that BetterLens gives f s
-- and we need s
lns (x -> Identity a) s
-- Note how we ignore the current value and
wrap 'a'
-- into Identity
-- set lns a = runIdentity . lns (Identity . const a)
:}
17. type BetterLens s a = forall f . Functor f => (a -> f a) -> s -> f s
By selecting functor for f above,Identity
:{
-- The lens library calls 'modify' as 'over'
let over :: BetterLens s a -> (a -> a) -> s -> s
over lns f = runIdentity . lns (Identity . f)
:}
18. type BetterLens s a = forall f . Functor f => (a -> f a) -> s -> f s
Now, how can this be useful for view?
view is of the type BetterLens s a -> (s -> a)
BetterLens is a type alias for a function that returns f
s)
What we need is a
So, we need something that goes from f s to a
How are we going to achieve that?
Can we somehow encode a into f?
19. INTRODUCINGTHECONSTFUNCTOR
type BetterLens s a = forall f . Functor f => (a -> f a) -> s -> f s
Remember the const function we saw earlier?
const :: x -> y -> x
-- a function that takes two values, ignores the second and
-- gives back the first
The ( Functor is a functor that ignores its argument,
just like the const function did
Const v)
newtype Const x y = Const x
getConst :: Const x y -> x
getConst (Const x) = x
instance Functor (Const v) where
fmap f (Const x) = Const x
-- note that f is unused
We can use the Const functor to encode a into f
20. type BetterLens s a = forall f . Functor f => (a -> f a) -> s -> f s
By selecting Const functor for f above,
:{
let view :: BetterLens s a -> (s -> a)
view lns s = getConst $ lns Const s
-- here, Const has type a -> Const a a
-- So, if Const a a is being used as the first argum
ent
-- of lns, it must be of type f a, with f being Cons
t a
:}
22. LENSFORTHEFRAMEHEADERCLASSWE
SAWBEFORE
type BetterLens s a = forall f . Functor f => (a -> f a) -> s -> f s
type FrameNumber = Integer
type TimeStamp = Integer
data FrameHeader = FrameHeader {
_frameNum :: FrameNumber,
_ts :: TimeStamp
} deriving Show
frameNum :: BetterLens FrameHeader FrameNumber
-- frameNum :: Functor f => (FrameNumber -> f FrameNumber) ->
-- (FrameHeader -> f FrameHeader)
frameNum fn (FrameHeader frameNum' ts') = fmap (frameNum'' -> FrameHeader frameNum''
ts')
(fn frameNum')
23. SOMENOTES
The getConst doesn't have runtime cost
True given that the lens impl. (e.g. frameNum) and view
are inlined
Note that lenses do compose
24. COMPOSINGLENSES
type BetterLens s a = forall f . Functor f => (a -> f a) -> s -> f s
composeL :: BetterLens w1 w2 -> BetterLens w2 a -> BetterLens w1 a
We can rewrite this
-- lens1 :: (w2 -> f w2> -> (w1 -> f w1)
type lens1 = BetterLens w1 w2
-- lens2 :: (a -> f a> -> (w2 -> f w2)
type lens2 = BetterLens w2 a
-- tada
lens1 . lens2 :: (a -> f a) -> (w1 -> f w1)
So, lens composition is simply function composition
25. WHAT'SINANAME
Let's change our type synonym's name to
type Lens' s a = forall f . Functor f => (a -> f a) -> s -> f s
This version of lens that takes two type variables as
parameters is defined in Lens library
There is also another version
type Lens s t a b = forall f . Functor f => (a -> f b) -> s -> f t
we won't go much into this
26. HELPERTOCREATELENSES
If we look at the lens we created above:
frameNum :: Lens' FrameHeader FrameNumber
frameNum fn (frameNum' ts') = fmap (frameNum'' -> FrameHeader frameNum'' ts')
(fn frameNum')
it can also be created using the lens helper function
lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b
-- for the case where type of a & b are same and type of s & t are same
lens :: (s -> a) -> (s -> a -> s) -> Lens s s a a
-- or
lens :: (s -> a) -> (s -> a -> s) -> Lens' s a
frameNum = lens ((FrameHeader fnum _) -> fnum)
((FrameHeader _ ts') newFnum -> FrameHeader newFnum ts)
27. AUTOMATICCREATIONOFLENSES
If we look at the lens we created above:
frameNum :: Lens' FrameHeader FrameNumber
frameNum fn (frameNum' ts') = fmap (frameNum'' -> FrameHeader frameNum'' ts')
(fn frameNum')
-- or
frameNum = lens ((FrameHeader fnum _) -> fnum)
((FrameHeader _ ts') newFnum -> FrameHeader newFnum ts)
the implementation of frameNum is just boiler-plate code.
lens library comes with template haskell code to automate
this
import Control.Lens.TH
data FrameHeader = FrameHeader {
_frameNum :: FrameNumber,
_ts :: TimeStamp
} deriving Show
makeLenses ''FrameHeader
This creates two lenses frameNum and ts like the ones
discussed previously
28. AUTOMATICCREATIONOFLENSES
WITHOUTAPPENDAGES
What if you don't like the underscores in field names that TH
requires?
There are many variations of TH code that you can use to
make these lenses
One variation is something like
import Control.Lens.TH
data FrameHeader = FrameHeader {
frameNum :: FrameNumber,
ts :: TimeStamp
} deriving Show
makeLensesFor [("frameNum", "frameNumLens"), ("ts", "tsLens")] ''FrameHeader
which creates lenses frameNumLens and tsLens from the
above data structure
29. SHORTHAND
The lens library contains many `infix` versions of functions
such as set and view to make it look more imperative.
We're not going over those in this session
30. ASIMPLETWISTTOLENSES
type Lens' s a = forall f . Functor f => (a -> f a) -> s -> f s
What if, we change f from a Functor to an Applicative?
type Traversal' s a = forall f . Applicative f => (a -> f a) -> s -> f s
We get an entirely different thing which can 'focus' on
multiple things of type a
31. REMINDEROFALENS
type Lens' s a = forall f . Functor f => (a -> f a) -> s -> f s
data Person = Person { firstName :: String,
lastName :: String,
age :: Integer }
fnLens :: Lens' Person String
fnLens fn (fname' lname' age') = fmap (fname'' -> Person fname'' lname' age') (fn fna
me')
32. ATRAVERSAL
type Lens' s a = forall f . Functor f => (a -> f a) -> s -> f s
type Traversal' s a = forall f . Applicative f => (a -> f a) -> s -> f s
nTraversal :: Traversal' Person String
-- nTraversal :: Applicative f => (String -> f String) -> (Person -> f Person)
nTraversal fn (Person fname' lname' age') = ... -- What can this be?
(fname'' lname'' -> Person fname'' lname'
' age')
... -- What can this be?
(fn fname')
... -- what can this be?
(fn lname')
33. AHA!
type Lens' s a = forall f . Functor f => (a -> f a) -> s -> f s
type Traversal' s a = forall f . Applicative f => (a -> f a) -> s -> f s
nTraversal :: Traversal' Person String
-- nTraversal :: Applicative f => (String -> f String) -> (Person -> f Person)
nTraversal fn (Person fname' lname' age') = pure
(fname'' lname'' -> Person fname'' lname'
' age')
<*>
(fn fname')
<*>
(fn lname')
34. WHATCANWEUSE
TRAVERSALSFOR?
Remember over? over can be used on traversals as well
type Traversal' s a = forall f . Applicative f => (a -> f a) -> s -> f s
over lns f = runIdentity . lns (Identity . f)
nTraversal :: Traversal' Person String
example:
-- Returns a Person with both firstName and lastName capitalized
over nTraversal (map toUpper) :: Person -> Person
36. PRISMS
Lens focuses on a single element of a complex data type
Traversal focuses on multiple elemnts of a complex data
type
the focussed elements are all of the same type
But, what if we have an algebriac data type that has many
constructors?
This is where Prism comes in
37. PRISMS
Prisms are like
_left :: Lens' (Either a b) a
>> view _left $ Left ()
()
>> view _left $ Right ()
error!
if the above is possible. Of course it is not possible.
38. PRISMS
So, can we use Maybe?
_left :: Lens' (Either a b) (Maybe a)
>> view _left $ Left ()
Just ()
>> view _left $ Right ()
Nothing
The problem is if we try to compose another Lens with it.
How can we?
39. PRISMS
So, we should not encode a Maybe into a Lens. This is why
Prism exists
Let's look at two functions that operate over prisms:
preview :: Prism' s a -> s -> Maybe a
review :: Prism' s a -> a -> s
Let's look at a built-in prism _Left
_Left :: Prism' (Either a b) a
>> preview _Left $ Left 10
Just 10
>> preview _Left $ Right "oops"
Nothing
40. APRISMUSAGE
data CruelData = CruelData {_cruelData :: String} deriving Show
data Greet = Hello Integer | Cruel CruelData | World String deriving Show
data Overall = Overall {_greet :: Greet} deriving Show
makeLenses ''CruelData
makeLenses ''Overall
-- TH
makePrisms ''Greet
-- only upcases if Greet is Cruel. Otherwise returns Overall as is
upcaseCruelty :: Overall -> Overall
upcaseCruelty = (greet . _Cruel . cruelData) %~ (map toUpper)
41. CONTROL.LENSOPERATORS
There are tons of operators (over 100) in the Control.Lens
library. Here's a general navigation rule:
Those that begin with ^ are view-like
Those that begin with ~ are over-like or set-like
Those that contain . are somehow basic (like view)
Those that contain % take functions (like over)
Those that begin with = are like the ones that have ~ but
apply modifications to a State monad
42. SOMECONTROL.LENS
FUNCTIONS
We've seen the following before:
view, set, over, traverse. There are others that are
useful
toListOf, preview, _1, … _9, _head, _tail, _init, etc
45. CREDITS
Several StackOverflow questions
Simon Peyton Jones's talk on Lenses
Lens starter tutorial in FPComplete
Talk on Lenses, Folds and Traversals by Edward Kmett,
author of the lens library