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
viewfor 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