1 Approach: Smash your boilerplate

2 Required features/Portability

The type-level typecase/TypeEq requires overlapping and undecidable
instances. The use of these extensions is limited to the library
only. The end user code needs no such extensions.

No need for Typeable, nor higher-rank types

3 Expressibility

Can do both producer and consumer functions: e.g., gsize (consumer),
replacing all Floats with Double (producer: the return type is
determined by the original term's type/structure). The return type
is computed by the typechecker rather than has to be specified by the
user. Pure producers (e.g., gminimum) are supported too.

Local redefinitions are possible, see Discussion.

4 Subset of data types covered

Whatever LDat instances are declared

All primitive and algebraic types are supported. Function
spaces. No Dynamic types. Polymorphic types can be handled, via
typeclass proxies.

5 Usage

Library Writer:
writes the core library (e.g., STApply class) and declares
the LDat class. This is done once and for all.

Power User: (new datatype or a new traversal strategy): defines
LDat instances

6 Error Messages

7 Amount of work per data type (Boilerplate)

instances of LDat, which are similar to Functor and could be
automatically derived from the definition of the data type.

8 Extensibility

Full

9 Reasoning

TODO ??? what does that mean?

10 Performance considerations

Although that is hidden from the end user, the library heavily relies
on the typeclasses. Thus the efficiency of the typeclass
implementation has direct impact.

Choosing the processor for each subterm is done at the compile
time. There is no passing and interpreting types at run time
(except when a compiler uses that to implement typeclasses).

11 Helpful extra features

TypeEq and TypeCast as built-ins would have helped.

Deriving for LDat (the same problem as with various SYB
approaches)

12 Discussion

Our generic functions are composed of specific and generic parts. A
specific part tells what to do if the input term happens to be of
a particular type. The generic part tells us what to do with all
other terms. The specific part is just an HList of ordinary functions,
with different argument types. If the type of the input term matches
the argument type of one of the functions in the list, we apply that
function. If no specific function applies, we do the generic action,
implicit in the traversal strategy (for example, apply the generic
function to the subterms and reduce the results).

SYB1, when traversing terms and invoking user functions for subterms
of a particular type, relies on a run-time typecase. The latter
requires run-time type representation, which is provided by the
typeclass Typeable. The typeclass implements the method `cast' for a
safe cast from the value of `generic type' to the value of the
specific type.

We observe that the typecase, at the type level, has always
existed in Haskell (although that fact was not perhaps realized until
HList). That typecase, which does not need any `cast' operation, is
the type equality predicate TypeEq. The type-level typecase is the
essence of the present approach. Since the handler for each subterm is
chosen statically, there is no need for run-time type representation.

The set of traversal strategies is extensible. To the
pre-defined gmap, reduction, and two-terms-in-lockstep strategies, the
programmer may at any time add a new class of traversals. The
message `Smash-along' shows one such extension, lazy gmap, and its
application to
mapping of terms that do not exist.

We should note the duality of `stapply' with respect to
typeclasses. Let us consider

The typeclass C declares the function fn overloaded over the argument
types. When typechecking an application "fn x", the compiler selects
the instance of "fn" by matching the type of "x" against the types of
C's instances. The same functionality can be implemented with
`stapply':

(The function `stapply' has the `default' clause, for all other data
types. One can do the same with the typeclasses, with the help of
overlapping instances).
With `stapply', the set of concrete processors is given explicitly as a
list rather than implicitly as a set of all typeclass instances in
effect. The implementation of `stapply' reifies typechecker's instance
selection algorithm. Since the concrete processors are given to stapply
in a list in a certain order, stapply easily deals with `overlapping'
instances: the first matching processing function is chosen.

The stapply approach is comparable to vlookup in
LIGD/Dynamics.lhs. However, in our case the `overriding', the
dispatch, is decided statically.

The overloaded function fn differs from fn' in that the set of fn
instances is open (and can be extended at any time by defining a new
instance of the class C). In contrast, all instances of fn' are
explicitly enumerated and their set is closed. This is however an artifact of
the particular definition fn'. We could just as well write

Since fn1_insts is a list, we can inspect the instances, rearrange
and remove them. These operations are not available with the typeclass
instances. Thus stapply approach is more flexible and
powerful. Finally, the set of stapply instances can be made open, as
described in
[http://www.haskell.org/pipermail/haskell/2006-October/018684.html
Infinite, open, statically constrained HLists]

The present approach shares with SYB the requirement that specific
processing functions (the ones that handle terms of particular types)
must be monomorphic. That requirement can be relaxed, as described in
[http://pobox.com/~oleg/ftp/Haskell/poly2.txt
Type-class overloaded functions: second-order typeclass programming
with backtracking]