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.
Examples
> 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

Utils

-- create the nested Map when it is missing:
Map.empty & at "hello" . non Map.empty . at "world" ?~ "!!!"
-- > fromList [("hello",fromList [("world","!!!")])]