Folding is one of the most important integral characteristics in many areas of applied mathematics: sum, mean, maximum, norm — all of these fold a collection of data into a single number. In Haskell a fold can be pulled off on any type that has an instance of , which rests on a single method:
class Foldable t where
foldMap :: Monoid w => (a -> w) -> t a -> w
-- (the class has other methods too, but foldMap is enough)
-- a container t implements Foldable
data Tree a = Empty | Leaf a | Node (Tree a) a (Tree a)
instance Semigroup (Tree a) where
Empty <> s = s
l <> Empty = l
Leaf x <> s = Node Empty x s
Node l x r <> s = Node l x (r <> s)
instance Monoid (Tree a) where mempty = Empty
instance Foldable Tree where
foldMap _ Empty = mempty
foldMap f (Leaf x) = f x
foldMap f (Node l x r) = foldMap f l <> f x <> foldMap f r
newtype Sum a = Sum a
instance Num a => Semigroup (Sum a) where Sum x <> Sum y = Sum (x + y)
instance Num a => Monoid (Sum a) where mempty = Sum 0
-- fold the tree 1,2,3 with the Sum monoid:
t = Node (Leaf 1) 2 (Leaf 3)
foldMap Sum t -- Sum 6 (the tree's shape collapsed into a single number)In fact, alone is enough to derive , , , , … But reduces the whole structure to a single value. What if at every element we need to run a computation — one that can fail, hit a config, accumulate a log — and still collect the results without losing the shape of the structure? That is what does.
Note the classes and in the declaration below: is so general that and are its special cases. Substitute the effect-free wrapper for the context — and degenerates into , so the container must be a . Substitute (an applicative whenever is a monoid) — and degenerates into , which means every is also a . That is why both classes end up as superclasses:
class (Functor t, Foldable t) => Traversable t where
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
sequenceA :: Applicative f => t (f a) -> f (t a)
traverse g = sequenceA . fmap g -- traverse and sequenceA are interdefinable
-- f = Identity (an effect-free wrapper):
(a -> Identity b) -> t a -> Identity (t b) ≅ (a -> b) -> t a -> t b -- fmap
-- f = Const w (an Applicative whenever w is a monoid):
(a -> Const w b) -> t a -> Const w (t b) ≅ (a -> w) -> t a -> w -- foldMap
-- half of an even number, failure otherwise:
half :: Int -> Maybe Int
half x = if even x then Just (x `div` 2) else Nothing
traverse half [2, 4, 6] -- Just [1,2,3]
traverse half [2, 3] -- NothingClearly, yields a list of boxes , and turns it inside out into a box with a list, ; and as soon as a single element returns , the whole result is .
It remains to see why the signature of demands precisely . In the example above glued together the contexts of neighbouring elements: with , while any wiped out the result. Such gluing takes exactly two skills: — to start the traversal with an “empty” effect — and — to attach the effect of the next element to what has already been accumulated. That is precisely the interface: is too weak here ( cannot merge two contexts into one), while is overkill ( lets the next step depend on the result of the previous one, but the route of the traversal is rigidly fixed by the shape of the structure anyway).
comes with laws, too. The most graphic one is identity: — an effect-free traversal changes neither the shape nor the values. The other two are composition (two consecutive traversals can be fused into one) and naturality (a traversal is consistent with swapping one applicative for another). Together they guarantee that visits every element exactly once and preserves the shape of the structure.
And so the six classes close the loop: combines values, / / apply functions in ever more powerful contexts, / traverse structures — and practically all the data-processing boilerplate boils down to these six.