Search code examples
haskellcategory-theorytraversable

Terminology of the Traversable concept


Why are we calling a flip of structure a "sequence" and why are we talking about "traverse" and "Traversal" ?

I'm adding the implementation in haskell of these concepts as a matter to discussion...

class (Functor t, Foldable t) => Traversable t where
    {-# MINIMAL traverse | sequenceA #-}

    -- | Map each element of a structure to an action, evaluate these actions
    -- from left to right, and collect the results. For a version that ignores
    -- the results see 'Data.Foldable.traverse_'.
    traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
    traverse f = sequenceA . fmap f

    -- | Evaluate each action in the structure from left to right, and
    -- and collect the results. For a version that ignores the results
    -- see 'Data.Foldable.sequenceA_'.
    sequenceA :: Applicative f => t (f a) -> f (t a)
    sequenceA = traverse id

Solution

  • Let's begin by considering fmap:

    fmap :: Functor t => (a -> b) -> t a -> t b
    

    We can describe what it does as finding all a values in a t a and applying a function to them. Note that, infinite structure shenanigans aside, the order in which the implementation of fmap reaches the a values to modify them doesn't matter as far as the final result is concerned.

    Now let's have a look at traverse:

    traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)
    

    Like fmap, traverse also involves applying a function to values found in a structure (it's no wonder traverse's forerunner was called mapM). traverse, however, also produces applicative effects for each of the a values (the f in a -> f b), and it involves combining those effects in some order to get the overall f (t b) result. In general (i.e. as long as f isn't a commutative applicative, such as Maybe), the order of effects affects the result. That being so, any Traversable instance corresponds to a specific order in which the values in the structure will be visited. The name "traverse" (that is, as Will Ness points out, "travel across or through") is meant to convey this sense of direction.

    On a related note, traverse can be decomposed into a plain mapping which produces effects followed by the sequencing of those effects...

    sequenceA :: (Applicative f, Traversable t) => t (f a) -> f (t a)
    
    traverse f = sequenceA . fmap f
    sequenceA = traverse id
    

    ... ergo the "sequence" name.

    It is also worth emphasising that traverse does capture various ways of going through a structure and doing things at each stop (cf. for being the name for flip traverse). In particular, we recover fmap by picking Identity as the Applicative functor (i.e. by not actually generating any effects), and foldMap (that is, list-like folding) by picking Monoid m => Const m instead. Things we can't do with traverse include dropping, duplicating or rearranging elements -- it doesn't allow tearing down a data structure in arbitrary ways like a general fold/catamorphism does. With traverse, we can move through a Traversable structure, but we can't reshape it.