Haskell Lens Traversals
A traversal is a lens shape that can touch many targets at once. It uses Applicative instead of Functor so it can combine many results.
A traversal can list both targets
toListOf runs bothT with a Const holding a list. Each target adds itself to that list.
{-# LANGUAGE RankNTypes #-}
newtype Const a b = Const { unConst :: a }
instance Functor (Const a) where
fmap _ (Const x) = Const x
instance Monoid a => Applicative (Const a) where
pure _ = Const mempty
(Const x) <*> (Const y) = Const (x <> y)
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
fmap f (Identity x) = Identity (f x)
instance Applicative Identity where
pure = Identity
(Identity f) <*> (Identity x) = Identity (f x)
type Traversal s a = forall f. Applicative f => (a -> f a) -> s -> f s
data Pair = Pair Int Int deriving Show
bothT :: Traversal Pair Int
bothT f (Pair x y) = Pair <$> f x <*> f y
toListOf :: Traversal s a -> s -> [a]
toListOf t s = unConst (t (\x -> Const [x]) s)
main :: IO ()
main = print (toListOf bothT (Pair 3 4))A traversal can update both targets
overT runs bothT with Identity and a function that adds ten. Both elements change at once.
{-# LANGUAGE RankNTypes #-}
newtype Const a b = Const { unConst :: a }
instance Functor (Const a) where
fmap _ (Const x) = Const x
instance Monoid a => Applicative (Const a) where
pure _ = Const mempty
(Const x) <*> (Const y) = Const (x <> y)
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
fmap f (Identity x) = Identity (f x)
instance Applicative Identity where
pure = Identity
(Identity f) <*> (Identity x) = Identity (f x)
type Traversal s a = forall f. Applicative f => (a -> f a) -> s -> f s
data Pair = Pair Int Int deriving Show
bothT :: Traversal Pair Int
bothT f (Pair x y) = Pair <$> f x <*> f y
overT :: Traversal s a -> (a -> a) -> s -> s
overT t g s = runIdentity (t (Identity . g) s)
main :: IO ()
main = print (overT bothT (+10) (Pair 3 4))A traversal can walk a whole list
listT visits every element of the list in order. Doubling each element gives the doubled list.
{-# LANGUAGE RankNTypes #-}
newtype Const a b = Const { unConst :: a }
instance Functor (Const a) where
fmap _ (Const x) = Const x
instance Monoid a => Applicative (Const a) where
pure _ = Const mempty
(Const x) <*> (Const y) = Const (x <> y)
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
fmap f (Identity x) = Identity (f x)
instance Applicative Identity where
pure = Identity
(Identity f) <*> (Identity x) = Identity (f x)
type Traversal s a = forall f. Applicative f => (a -> f a) -> s -> f s
listT :: Traversal [Int] Int
listT _ [] = pure []
listT f (x : xs) = (:) <$> f x <*> listT f xs
overT :: Traversal s a -> (a -> a) -> s -> s
overT t g s = runIdentity (t (Identity . g) s)
main :: IO ()
main = print (overT listT (*2) [1,2,3])Example
{-# LANGUAGE RankNTypes #-}
newtype Const a b = Const { unConst :: a }
instance Functor (Const a) where
fmap _ (Const x) = Const x
instance Monoid a => Applicative (Const a) where
pure _ = Const mempty
(Const x) <*> (Const y) = Const (x <> y)
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
fmap f (Identity x) = Identity (f x)
instance Applicative Identity where
pure = Identity
(Identity f) <*> (Identity x) = Identity (f x)
type Traversal s a = forall f. Applicative f => (a -> f a) -> s -> f s
data Pair = Pair Int Int deriving Show
bothT :: Traversal Pair Int
bothT f (Pair x y) = Pair <$> f x <*> f y
toListOf :: Traversal s a -> s -> [a]
toListOf t s = unConst (t (\x -> Const [x]) s)
overT :: Traversal s a -> (a -> a) -> s -> s
overT t g s = runIdentity (t (Identity . g) s)
main :: IO ()
main = do
print (toListOf bothT (Pair 3 4))
print (overT bothT (*2) (Pair 3 4))Key points
- Traversal: a function of the same shape as a lens but with an Applicative constraint.
- Applicative: a functor that can also combine wrapped values with pure and <*>.
- Const list: a Const holding a list collects every target into one list.
- Over: updating through a traversal applies the function to every target.
- Use a traversal to read all targets or update all targets in one pass.
In this example, toListOf first collects both elements of the pair into a list, then overT doubles both elements in one pass.
Note
a lens touches exactly one target while a traversal may touch zero or many targets.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Haskell Playground
Exercise
What does toListOf bothT (Pair 3 4) return?