User:Hamflask/sandbox

We already have studied four of the five type classes in the Prelude that can be used for data structure manipulation:,  ,   and. The fifth one is. To traverse means to walk across, and that is exactly what  generalises: walking across a structure, collecting results at each stop.

Functors made for walking
If traversing means walking across, though, we have been performing traversals for a long time already. Consider the following plausible  and   instances for the   from Other data structures:

walks across the tree, applies  to each element and collects the results by rebuilding the tree. Similarly,  walks across the tree, applies   to each element and collects the results by combining them with. and, however, are not enough to express all useful ways of traversing. For instance, suppose we have the following -encoded test for negative numbers...

... and we want to use it to implement...

... which gives back the original tree wrapped in  if there are no negative elements in it, and   otherwise. Neither  nor   on their own would help. Using  would replace the structure of the original tree with that of whatever   we pick for folding, and there is no way to encode the structure of   into a. As for,   might be attractive at first...

... but then we would need a way to turn a  of   into   a. If you squint hard enough, that looks somewhat like a fold. Instead, however, of merely combining the values with  (which destroys the tree structure), we need to combine the   contexts of the values and recreate the tree structure within the combined context. Fortunately, there is a type class that is essentially about combining  contexts:. , in turn, leads us to the class we need:.

is to  contexts what   is to   values. From that point of view,  is analogous to   − it creates an applicative summary of the contexts within a structure, and then rebuilds the structure in the new context. is the function we were looking for:

These are the methods of :

If  is analogous to ,   is analogous to. They can be defined in terms of each other, and therefore a minimal implementation of  just needs to supply one of them:

Rewriting the list instance using  makes the parallels with   and   obvious:

In general, it is better to write  when implementing , as the default definition of   performs, in principle, two runs across the structure (one for   and another for  ).

We can cleanly define  directly in terms of  :

Interpretations of
structures can be walked over using the applicative functor of your choice. The type of ...

... resembles that of mapping functions we have seen in other classes. Rather than using its function argument to insert functorial contexts under the original structure (as might be done with ) or to modify the structure itself (as   does),   adds an extra layer of context on the top of the structure. Said in another way,  allows for effectful traversals − traversals which produce an overall effect (i.e. the new outer layer of context).

If the structure below the new layer is recoverable at all, it will match the original structure (the values might have changed, of course). Here is an example involving nested lists:

The inner lists retain the structure the original list − all of them have four elements. The outer list is the new layer, corresponding to the introduction of nondeterminism through allowing each element to vary from zero to its (original) value.

We can also understand  by focusing on   and how it distributes context.

In this example,  can be seen distributing the old outer structure into the new outer structure, and so the new inner lists have two elements, just like the old outer list. The new outer structure is a list of twelve elements, which is exactly what you would expect from combining with  one list of four elements with another of three elements. One interesting aspect of the distribution perspective is how it helps making sense of why certain functors cannot possibly have instances of  (how would one distribute an   action? Or a function?).

The laws
Sensible instances of  have a set of laws to follow. There are the following two laws:

Plus a bonus law, which is guaranteed to hold:

Those laws are not exactly self-explanatory, so let's have a closer look at them. Starting from the last one: an applicative homomorphism is a function which preserves the  operations, so that:

Note that not only this definition is analogous to the one of monoid homomorphisms which we have seen earlier on but also that the naturality law mirrors exactly the property about  and monoid homomorphisms seen in the chapter about.

The identity law involves, the dummy functor:

The law says that all traversing with the  constructor does is wrapping the structure with , which amounts to doing nothing (as the original structure can be trivially recovered with  ). The  constructor is thus the identity traversal, which is very reasonable indeed.

The composition law, in turn, is stated in terms of the  functor:

performs composition of functors. Composing two s results in a , and composing two  s results in an. The instances are the obvious ones, threading the methods one further functorial layer down.

The composition law states that it doesn't matter whether we perform two traversals separately (right side of the equation) or compose them in order to walk across the structure only once (left side). It is analogous, for instance, to the second functor law. The s are needed because the second traversal (or the second part of the traversal, for the left side of the equation) happens below the layer of structure added by the first (part). is needed so that the composed traversal is applied to the correct layer.

and  are available from  and  respectively.

The laws can also be formulated in terms of :

Though it's not immediately obvious, several desirable characteristics of traversals follow from the laws, including :


 * Traversals do not skip elements.
 * Traversals do not visit elements more than once.
 * Traversals cannot modify the original structure (it is either preserved or fully destroyed).
 * Traversals cannot modify the original structure (it is either preserved or fully destroyed).

Recovering and
We still have not justified the  and   class constraints of. The reason for them is very simple: as long as the  instance follows the laws   is enough to implement both   and. For, all we need is to use   to make a traversal out of an arbitrary function:

To recover, we need to introduce a third utility functor:   from :

is a constant functor. A value of type  does not actually contain a   value. Rather, it holds an  value which is unaffected by. For our current purposes, the truly interesting instance is the  one

simply combines the values in each context with. We can exploit that to make a traversal out of any  function that we might pass to. Thanks to the instance above, the traversal then becomes a fold:

We have just recovered from  two functions which on the surface appear to be entirely different, and all we had to do was pick two different functors. That is a taste of how powerful an abstraction functors are.

Non-equivalence of Traversable and Foldable
Above, we discovered that  can be implemented in terms of. However, is the converse true? That is, can  be implemented in terms of  ?

Let's take another look at the  and   instances for the   type considered previously.

While the definitions are very similar, we see that  rebuilds the tree structure (as required by the   laws) within the resulting   context, while   simply combines the resulting monoid values using. As we saw above,  is an   functor that mimics the behavior of a monoid, and this is what allowed us to define   in terms of. Therefore, implementing  in terms of   boils down to finding a monoid that mimics the behavior of.

Rewriting the above instances with each case grouped together makes things a bit more clear:

The obvious attempt would be to let  and define a monoid such that. However, this will not work! Because  is not associative,   is also not associative and therefore is not a valid definition for. In addition, there is no reasonable definition for  in this case, since there is no empty. It turns out that implementing  in terms   alone is simply impossible for , and hence cannot be done in general.

We've shown that implementing  in terms of   alone is impossible for , but what about other   structures? The difficulty with  is that   needs to rebuild the structure by mapping each of its constructors over the functor contexts. provides the flexibility to make this work for constructors that have any number of arguments, and it does not impose any restraints on the functions (such as associativity). , on the other hand, does not support constructing types with anything more than an empty constructor (e.g.  or  ) via , a singleton constructor (e.g.   or  ) via the   in  , and a binary associative constructor (e.g.  ) via   such that   is a left and right identity. The most complex type that can be constructed by  is therefore a list, and indeed a list traversal can be implemented as a fold using an appropriate   instance: