🎓 A1Learner
🔍
0 of 47 lessons completed

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?