Lenses
Usage
{-# LANGUAGE TemplateHaskell #-}
data Person = Person
{ _firstname :: String
, _surname :: String
}
-- building lenses for _firstname and _surname
makeLenses ''Person
-- create an HasPerson class with the `firstname` and `surname` optics
makeClassy ''Person
-- create an HasFirstname and HasSurname class
data Person = Person
{ _PersonFirstname :: String
, _PersonSurname :: String
}
makeField ''Person
Lens
A lens is a first-class reference to a subpart of some data type.
Lens' s a
operates on a container s
and put the focus on 'a'.
Lens' s t a b
when you replace a
in s
with b
, its type changes to t
.
Note that lenses are not accessors
but focusers
. It focus on a particular location of a structure. These are the types we want for view
, set
and over/update
:
view :: Lens' s a -> s -> a
set :: Lens' s a -> a -> s -> s
over :: Lens' s a -> (a -> a) -> s -> s
The big insight is the fact that the Lens'
type can be implemented as an unique type that works for all 3 methods (given we add a functor constraint). It is actually a type synonym for:
type Lens' s a = forall f. Functor f => (a -> f a) -> s -> f s (1)
1 | This is a kind of a lifting from the element (a → f a) to the container (s → f s) |
Lenses form a category where . is composition and id is the identity.
|
> over _1 (++ "!!!") ("goal", "the crowd goes wild") > ("goal", "the crowd goes wild") & _1 %~ (<> "!!!") (1) ("goal!!!", "the crowd goes wild") > ("world", "world") & _1 .~ "hello" & _2 .~ "hello" (1) > ([1], 2) & _1 <>~ [2,3,4]
1 | & allows to start the expression from s and then compose.
It is defined as the reverse of $ operator. |
Common operators
^. |
view |
^? |
preview |
^.. |
toListOf |
.~ |
set |
%~ |
over |
<>~ |
apply the func '<>' |
.= |
state monad view |
Traverse
Traversals are Lenses which focus on multiple targets simultaneously. We actually don’t know how many targets they might be focusing on: it could be exactly 1 (like a Lens) or maybe 0 (like a Prism) or several. In that regard, a traversal is a like a Lens' except weaker (more general):
type Traversal' a b =
forall f . (Applicative f) => (b -> f b) -> (a -> f a)
firstOf/lastOf traverse :: Traversable t => t a -> Maybe a
> firstOf traverse [1,2,3]
1
> [1..8] & lastOf traverse
8
toListOf (^..)
-
view list of targets
preview (^?)
-
like
view
for Prism’s or Traversal’s. It handles access that focuses on either 0 or 1 targets.
Prims
Prisms are kind of like Lenses that can fail or miss.
Note how the monoid instance of String allows us to get a native String from this expression:
> s = (Left "hello", 5) > s ^. _1._Left "hello" > s ^. _1._Right ""
But without a monoid instance it cannot work and the (^?)
is necessary:
> s = (Left 5, 5)
> s ^? _1._Left
Just 5
> s ^? _1._Right
Nothing
> :t preview _Right (Right 1)
Num b => Maybe b