A Lens provides access to a particular part of a data structure.
Lenses express a high-level pattern for composition and in that sense belong firmly in Chapter 3, Patterns for Composition. However, the concept of Lens
is also deeply entwined with Foldable
and Traversable
, and so we describe it in this chapter instead.
Lenses relate to the getter and setter functions, which also describe access to parts of data structures. To find our way to the Lens abstraction (as per Edward Kmett's Lens library), we'll start by writing a getter and setter to access the root node of a tree.
Let's return to our Tree
type from earlier:
data Tree a = Node a (Tree a) (Tree a) | Leaf a deriving Show intTree = Node 2 (Leaf 3) (Node 5 (Leaf 7) (Leaf 11)) listTree = Node [1 ,1 ] (Leaf [2,1 ]) (Node [3,2] (Leaf [5,2]) (Leaf [7,4])) tupleTree = Node (1 ,1 ) (Leaf (2,1 )) (Node (3,2) (Leaf (5,2)) (Leaf (7,4)))
Let's start by writing generic getter and setter functions:
getRoot :: Tree a -> a getRoot (Leaf z) = z getRoot (Node z _ _) = z setRoot :: Tree a -> a -> Tree a setRoot (Leaf z) x = Leaf x setRoot (Node z l r) x = Node x l r main = do print $ getRoot intTree print $ setRoot intTree 11 print $ getRoot (setRoot intTree 11)
If, instead of setting a value, we want to pass in a setter function, we can define a modifier function based on the getter and setter functions:
fmapRoot :: (a -> a) -> Tree a -> Tree a fmapRoot f tree = setRoot tree newRoot where newRoot = f (getRoot tree)
We have to get the root of the Tree
, apply the function, and then set the result. This double work is akin to the double traversal we saw when writing traverse
in terms of sequenceA
. In that case, we resolved the issue by first defining traverse
and then writing sequenceA
in terms of traverse
.
We can do the same thing here, by writing fmapRoot
to work in a single step (and then rewriting setRoot'
in terms of fmapRoot'
):
fmapRoot' :: (a -> a) -> Tree a -> Tree a
fmapRoot' f (Leaf z) = Leaf (f z)
fmapRoot' f (Node z l r) = Node (f z) l r
setRoot' :: Tree a -> a -> Tree a
setRoot' tree x = fmapRoot' (\_ -> x) tree
main = do
print $ setRoot' intTree 11
print $ fmapRoot' (*2) intTree
The fmapRoot'
function delivers a function to a particular part of the structure and returns the same structure:
fmapRoot' :: (a -> a) -> Tree a -> Tree a
To make provision for IO we need to define the following function:
fmapRootIO :: (a -> IO a) -> Tree a -> IO (Tree a)
We can generalize this beyond IO to all monads:
fmapM :: (a -> m a) -> Tree a -> m (Tree a)
It turns out that if we relax the requirement for Monad
and instead generalize to all the Functor
container types f'
, then we get a simple van Laarhoven Lens of type:
type Lens' s a = Functor f' => (a -> f' a) -> s -> f' s
The remarkable thing about a van Laarhoven Lens is that, given the above function type, we also gain get
, set
, fmap
, and mapM
, along with many other functions and operators.
The Lens
function type signature is all that it takes to make something a Lens that can be used with the Lens
library. It is unusual to use a type signature as the"primary interface" for a library. The immediate benefit is that we can define a Lens without referring to the Lens
library.
We'll explore more benefits and costs to this approach, but first let's write a few lenses for our tree.
(The derivation of the Lens abstraction used here is based on Jakub Arnold's "Lens tutorial", which is available at http://blog.jakubarnold.cz/2014/07/14/lens-tutorial-introduction-part-1.html.)
A Lens is said to provide focus on an element in a data structure.
Our first Lens will focus on the root node of a Tree
datastructure. Using the Lens type signature as our guide, we arrive at the following:
lens':: Functor f => (a -> f' a) -> s -> f' s root :: Functor f' => (a -> f' a) -> Tree a -> f' (Tree a)
This is still not very tangible; fmapRootIO
is easier to understand, with the Functor f'
being IO
:
fmapRootIO :: (a -> IO a) -> Tree a -> IO (Tree a)
fmapRootIO g (Leaf z) = (g z) >>= return . Leaf
fmapRootIO g (Node z l r) = (g z) >>= return . (x -> Node x l r)
displayM x = print x >> return x
main = fmapRootIO displayM intTree
If we drop down from Monad
into Functor
, we have a Lens for the root of a Tree
:
root :: Functor f' => (a -> f' a) -> Tree a -> f' (Tree a) root g (Node z l r) = fmap (x > Node x l r) (g z) root g (Leaf z) = fmap Leaf (g z)
Since Monad
is a Functor
, this function also works with Monadic functions:
main = root displayM intTree
Because root
is a Lens, the Lens library enables the following:
-– import Control.Lens main = do -- GET print $ view root listTree print $ view root intTree -- SET print $ set root [42] listTree print $ set root 42 intTree -- FMAP print $ over root (+11) intTree
The over
function is the Lens way of mapping a function to a Functor
value.
Let's define a Lens that provides focus on the rightmost leaf of a Tree
data structure:
rightMost :: Functor f' => (a -> f' a) -> Tree a -> f' (Tree a) rightMost g (Node z l r) = fmap ( ' -> Node z l r') (rightMost g r) rightMost g (Leaf z) = fmap (x -> Leaf x) (g z)
The Lens
library provides several lenses for Tuple
(for example, _1
which brings focus to the first Tuple
element). We can compose our rightMost
Lens with these Tuple
lenses:
main = do print $ view rightMost tupleTree print $ set rightMost (0,0) tupleTree -- Compose Getters and Setters print $ view (rightMost._1) tupleTree print $ set (rightMost._1) 0 tupleTree print $ over (rightMost._1) (*100) tupleTree
A Lens can serve as a getter, setter, or modifier.
Moreover, we compose Lenses using the regular function composition (.)
! Note that the order of composition is the reverse of regular function composition: in (rightMost._1)
, the rightMost
Lens is applied before the _1
Lens.
A Lens focuses on one part of a data structure, not several; for example, a Lens cannot focus on all the leaves of a Tree
:
set leaves 0 intTree over leaves (+1) intTree
To focus on more than one part of a structure, we need a Traversal
, the Lens generalization of Traversable
. While Lens relies on Functor
, Lens Traversal
relies on Applicative
. Other than this, the signatures are exactly the same:
traversal :: Applicative f' => (a -> f' a) -> Tree a -> f' (Tree a) lens :: Functor f'=> (a -> f' a) -> Tree a -> f' (Tree a)
Let’s define a leaves Traversal
that delivers a setter function to all the leaves of a Tree
:
leaves :: Applicative f' => (a -> f' a) -> Tree a -> f' (Tree a)
leaves g (Node z l r)
= Node z <$> leaves g l <*> leaves g r
leaves g (Leaf z)
= Leaf <$> (g z)
We can use set and over with our new Traversal
:
set leaves 0 intTree over leaves (+1) intTree
Traversals compose seamlessly with Lenses
:
main = do -- Compose Traversal + Lens print $ over (leaves._1) (*100) tupleTree -- Compose Traversal + Traversal print $ over (leaves.both) (*100) tupleTree -- map over each elem in target container (e.g. list) print $ over (leaves.mapped) (*(-1)) listTree -- Traversal with effects mapMOf leaves displayM tupleTree
(both
is a Tuple Traversal
that focuses on both tuple
elements).
The Lens.Traversal
abstraction lifts Traversable
into the realm of lenses, while Lens.Fold
does the same for Foldable
:
main = do print $ sumOf leaves intTree print $ anyOf leaves (>0) intTree
As is the case for Foldable
sum and foldMap
, we can write Lens sumOf
in terms of foldMapOf
:
getSum $ foldMapOf lens Sum
where foldMapOf
is a generalization of Foldable.foldMap
.
We've used only simple Lenses; a fully parameterized Lens would allow for replacing parts of a data structure with different types:
type Lens s t a b = Functor f' => (a -> f' b) -> s -> f' t –- vs simple Lens type Lens' s a = Lens s s a a
Lens library function names do their best not to clash with existing names; for example, postfixing of idiomatic function names with "Of
" (sumOf
, mapMOf
, and so on), or using different verb forms such as droppingWhile
instead of dropWhile
. While this creates a burden in terms of having to learn new variations, it does have a big plus: allowing for easy unqualified import of the Lens
library.
By leaving the Lens function type transparent (and not obfuscating it with a newtype
), we get Traversals
by simply swapping out Functor
for Applicative
in the Lens type definition. We also get to define lenses without having to reference the Lens
library. On the downside, Lens type signatures can be bewildering at first sight. They form a language of their own that requires an effort to get used to, for example:
mapMOf :: Profunctor p => Over p (WrappedMonad m) s t a b -> p a (m b) -> s -> m t foldMapOf :: Profunctor p => Accessing p r s a -> p a r -> s -> r
On the surface, the Lens
library gives us composable getters and setters, but there is much more to lenses than that. By generalizing Foldable
and Traversable
into Lens abstractions, the Lens
library lifts getters, setters, lenses, and Traversals into a unified framework in which they all compose together.
The Lens
library has been criticized for not reflecting idiomatic Haskell and for simply taking on too much responsibility. Nevertheless, Edward Kmett's Lens library is a sprawling masterpiece that is sure to leave a lasting impact on Haskell.