everywhere

everywhere'

transform

everywhere'

transform'

everywhere'

transform



data Exp = Lit Int | Add Exp Exp | Mul Exp Exp



f (Mul (Lit 1) x) = x

f (Mul (Lit 3) x) = Add x (Add x x)

f x = x



transform f

3 * (1 * 2)

2 + (2 + 2)

transform

descend



transform f x = f (descend (transform f) x)



f

f



f (Mul (Lit 1) x) = x

f (Mul (Lit 3) x) = f (Add x (f (Add x x)))

f x = x



transform f

transform

transform'

transform'



transform' f x = descend (transform' f) (f x)



f

transform

transform'

f

x

x

Add

transform'

x

transform'

f

1 * (1 * 1)

1 * 1

transform'

f



f (Mul (Add _ _) _) = ...

f (Add _ _) = Mul ...



Add

Mul

transform'

descend

descend



f (Mul (Lit 1) x) = f x

f (Mul (Lit 3) x) = Add (f x) (Add (f x) (f x))

f x = descend f x



f

f x

f

transform'

everywhere'

transform'

transform

everywhere'

everywhere

everywhere'

descend

The Uniplate library contains many traversal operations, some based on functions in SYB (Scrap Your Boilerplate) . SYB providesfor bottom-up traversals andfor top-down traversals. Uniplate providesfor bottom-up traversals, but has no operation similar to. This article explains why I didn't include aoperation, and why I believe that most uses ofare probably incorrect.Theoperation applies a function to every node in a tree, starting at the bottom and working upwards. To give a simple example:Callingon the inputgives. We can writein terms of the Uniplate operation, which applies a function to every child node:On every application ofthe argument always consists of a root node which has not yet been processed, along with child nodes that have been processed. My thesis explains how we can guarantee a transform reaches a fixed point, by callingagain before every constructor on the RHS of any clause:Nowis guaranteed to reach a fixed point. Theoperation is predictable, and naturally defines bottom-up transformations matching the users intention. Unfortunately, the ordering and predictability ofis significantly more subtle. We can easily defineHere the transformation is applied to every child of the result of. Withevery node is processed exactly once, but withsome nodes are processed multiple times, and some are not processed at all. The first clause of, which returns, does not result in the root ofbeing processed. Similarly, our second cause returns two levels of constructor, causing the innerto be both generated and then processed.When people look atthe intuitive feeling tends to be that all the variables on the RHS will be processed (i.e.), which in many cases mostly matches the behaviour of. Being mostly correct means that many tests work, but certain cases fail - with our function, the first example works, butresults in. The original version of Uniplate contained, and I spent an entire day tracking down a bug whose cause turned out to be a function whose RHS was just a variable, much like the first clause ofBefore describing the solution to top-down transformations, it's interesting to first explore where top-down transformations are necessary. I have identified two cases, but there many be more. Firstly, top-down transformations are useful when one LHS is contained within another LHS, and you wish to favour the larger LHS. For example:Here the second LHS is contained within the first. If we perform a bottom-up transformation then the innerexpression will be transformed to, and the first clause will never match. Changing to a top-down transformation allows the larger rule to match first.Secondly, top-down transformations are useful when some information about ancestors is accumulated as you proceed downwards. The typical example is a language with let expressions which builds up a set of bindings as it proceeds downwards, these bindings then affect which transformations are made. Sadly,cannot express such a transformation.The solution to both problems is to use theoperation, and explicitly control the recursive step. We can rewrite the original example usingHere we explicitly callto continue the transformation. The intuition that all variables are transformed is now stated explicitly. In this particular example we can also common-up the three subexpressionsin the second clause, giving a more efficient transformation. If needed, we can add an extra argument toto pass down information from the ancestors.After experience with Uniplate I decided that usingcorrectly was difficult. I looked at all my uses ofand found that a few of them had subtle bugs, and in most cases usingwould have done exactly the same job. I looked at all the code on Hackage (several years ago) and found only six uses of, all of which could be replaced withwithout changing the meaning. I consider top-down transformations in the style ofto be dangerous, and strongly recommend using operations likeinstead.Are you interested in either generics programming or Haskell? Are you going to ICFP 2010 ? Why not: Submit a paper to the Workshop on Generic Programming - Generic programming is about making programs more adaptable by making them more general. This workshop brings together leading researchers and practitioners in generic programming from around the world, and features papers capturing the state of the art in this important area. Offer a talk at the Haskell Implementors Workshop - an informal gathering of people involved in the design and development of Haskell implementations, tools, libraries, and supporting infrastructure.I'm on the program committee for both, and look forward to receiving your contributions.