The Wayback Machine - https://web.archive.org/web/20041207211740/http://www.cs.vu.nl:80/boilerplate/

Scrap your boilerplate ... in Haskell

(Note: you need GHC built from CVS to use the latest version of this stuff.
GHC 6.0 covers the TLDI 2003 paper. Note that class Term is meanwhile called Data.
GHC 6.2 covers the ICFP 2004 paper, too, modulo tiny deviations regarding the reflection API.)

The "Scrap your boilerplate" approach is a lightweight generic programming approach for Haskell. The approach is supported in the GHC >= 6.0 implementation of Haskell. Using this approach, you can write generic functions such as traversal schemes (e.g., everywhere and everything), as well as generic read, generic show and generic equality (i.e., gread, gshow, and geq). This approach is based on just a few primitives for type-safe cast and processing constructor applications.

This page provides you with all the information about the boilerplate approach, with documentation, examples and others. If you want to get an idea, please have a look at the paradise benchmark, or just work through the index below to find what you are looking for.


Index


An example --- the paradise benchmark

To give you an idea, put your XML hat on, think of a BIG XML schema for the organisational data structure of a company, and now implement the paradise benchmark, say increase all salaries by a certain amount. This is how you can approach to paradise in Haskell (with GHC extensions):


-- Increase salary by percentage
increase :: Float -> Company -> Company
increase k = everywhere (mkT (incS k))

-- "interesting" code for increase
incS :: Float -> Salary -> Salary
incS k (S s) = S (s * (1+k))

That is, you write a function increase that takes a percentage k and a company. You define this function to apply a trivial worker incS on salaries whenever traversal hits on a salary. Your traversal will eventually look at all nodes; this is achieved by the traversal scheme everywhere. To lift the worker incS to the generic level, you make a transformation from it using mkT. That's it. The two combinators everywhere and mkT are defined in GHC's library Data.Generics. The only prerequisite for generic programming on your company datatypes is that you add corresponding deriving clauses to the datatype declarations:


data Company  = C [Dept]               deriving (Eq, Show, Typeable, Data)
data Dept     = D Name Manager [Unit]  deriving (Eq, Show, Typeable, Data)
data Unit     = PU Employee | DU Dept  deriving (Eq, Show, Typeable, Data)
data Employee = E Person Salary        deriving (Eq, Show, Typeable, Data)
data Person   = P Name Address         deriving (Eq, Show, Typeable, Data)
data Salary   = S Float                deriving (Eq, Show, Typeable, Data)
...

So you add deriving (Typeable, Data) to each datatype just in the same way as you normally trigger derivation of instances for Eq and Show. The classes Typeable and Data comprise members for type-safe cast and processing constructor applications. These are the boilerplate primitives for generic programming.


The boilerplate papers

Paper I: "Scrap your boilerplate: a practical design pattern for generic programming"
by Ralf Lämmel and Simon Peyton-Jones,
appeared in Proceedings of TLDI 2003, ACM Press

Abstract: We describe a design pattern for writing programs that traverse data structures built from rich mutually-recursive data types. Such programs often have a great deal of "boilerplate" code that simply walks the structure, hiding a small amount of "real" code that constitutes the reason for the traversal. Our technique allows most of this boilerplate to be written once and for all, or even generated mechanically, leaving the programmer free to concentrate on the important part of the algorithm. These generic programs are much more adaptive when faced with data structure evolution because they contain many fewer lines of type-specific code. Our approach is simple to understand, reasonably efficient, and it handles all the data types found in conventional functional programming languages. It makes essential use of rank-2 polymorphism, an extension found in some implementations of Haskell. Further it relies on a simple type-safe cast operator.

Paper II: "Scrap more boilerplate: reflection, zips, and generalised casts"
by Ralf Lämmel and Simon Peyton-Jones,
to appear in Proceedings of ICFP 2004, ACM Press

Abstract: Writing boilerplate code is a royal pain. Generic programming promises to alleviate this pain by allowing the programmer to write a generic ``recipe'' for boilerplate code, and use that recipe in many places. In earlier work we introduced the ``Scrap your boilerplate'' approach to generic programming, which exploits Haskell's existing type-class mechanism to support generic transformations and queries. This paper completes the picture. We add a few extra ``introspective'' or ``reflective'' facilities, that together support a rich variety of serialisation and de-serialisation. We also show how to perform generic ``zips'', which at first appear to be somewhat tricky in our framework. Lastly, we generalise the ability to over-ride a generic function with a type-specific one. All of this can be supported in Haskell with independently-useful extensions: higher-rank types and type-safe cast. The GHC implementation of Haskell readily derives the required type classes for user-defined data types.

Download:


A suite of examples

For the examples, we always list a Haskell module, maybe some datatypes, and maybe the intended output of the program -- if relevant for the example at hand. Some minimal explanation of the purpose of the example is normally found in the Haskell module. The examples shown are contained like this in the GHC testsuite. If you want to get your examples included, please contact the authors.

FAQ --- frequently asked questions

What's hot?
With the completion of the second boilerplate paper we have covered all "urgent" forms of generic programming idioms: term traversal, serialisation, de-serialisation, zippers, test-data generators, and a few more. A hot topic is now to make generic functions as efficient as possible. Among other things, we plan to look into making traversals more efficient by cutting off traversal for irrelevant subterms. To this end, Template Haskell might come in handy. Another hot topic is about a generics-based XML binding. At the API design level, we look into static and modular type case, which will complement run-time type case as favoured in the first two papers. Static and modular type case will be covered in the third SYB paper; see some code snippets here.

How does the boilerplate approach relate to Strafunski?
The boilerplate approach emerged from the style of functional strategies as used in Strafunski. This can best traced in "Strategic polymorphism requires just two combinators", where it is shown how strategies can be defined in terms of the primitives chosen for the boilerplate approach. Strafunski as a distribution provides more than a generic programming approach: it comprises themes, examples and tools that support generic programming in the application domain of language processing. Also, the strategic programming style favoured in Strafunksi differs from the boilerplate approach in so far that the former is based on an ADT for functional strategies whereas the latter favours freewheeling higher-order functional programming.

How does the boilerplate approach relate to Generic Haskell?
This question is addressed in some detail in the boilerplate paper and in papers on Strafunski. In essence, Generic Haskell provides a very general mechanism to define generic functions by induction on the type structure on the basis of designated constructs. By contrast, the boilerplate approach is pretty much lightweight while focusing on term traversal as the prime idiom of generic programming. Also, the boilerplate approach aims at a smooth integration of generic programming with Haskell. Furthermore, the boilerplate approach supports a combinator style of generic programming.

Can the Data.Generics modules be used with hugs?
The Data.Generics modules were not tested with hugs, but it should be possible to make them work with hugs; in particular the required rank-2 types are readily supported in hugs. While the support for deriving instances for the type classes Typeable and Data comes readily with GHC, in the case of hugs, it can be provided with DrIFT; see the "useful links" section for corresponding source distributions. If you are interested in helping with making the boilerplate approach work for hugs, please contact the authors.

Useful links


Authors


Page last updated November 08, 2004.
Web site maintained by Ralf Lämmel.