In the early days Programatica project, about three years ago, there was a plan to experiment with several extensions of Haskell in parallel. Work started on an extensible Haskell front-end, using the Haskell Source library supplied with GHC as a starting point.
into a two-level structure, with a set of nonrecursive definitions and a separate set of knot-tying recursive definitionsdata Id = ... data Exp = ... Exp ... Decl ... Pat ... Id ... Type ... data Decl = ... data Pat = ... data Type = ...
This allows the abstract syntax for an extension of the language to be formed by taking the union of the base structure with an extension and tying a new recursive knot. For example, to extend the syntax of expressions, the following definitions could be used:data Id = data E e d p t = ... e ... d ... p ... Id ... t ... data D e d p t = ... data P p = ... data T t = ...newtype Exp = Exp (E Exp Decl Pat Type) newtype Decl = Decl (D Exp Decl Pat Type) newtype Pat = ... newtype Type = ...
To allow functions traversing abstract syntax to be reused in extensions, they need to be refactored in a similar way. Since no refactoring tools for Haskell were available, this refactoring was achieved by tedious manual work.data E2 e = ... newtype Exp2 = Exp2 (Either (E Exp2 Decl2 Pat Type) (E2 Exp2)) newtype Decl2 = Decl2 (D Exp2 Decl2 Pat Type)
For reusable functions, we made use of the class system in Haskell, turning every function intended to be extensible into a method of a class. For example, there was a function to compute the free variables of a piece of syntax
class Free s where free :: s -> [Id]instance Free Exp where free (Exp e) = free e instance (Free e,Free d, ...) => Free (E e d p t) where free = ... ...
In this step, also the definition of the classes with methods involving identifiers needed to be changed:data Id = data E i e d p t = ... e ... d ... p ... i ... t ... data D i e d p t = ... data P i p = ... data T i t = ...newtype Exp i = Exp (E (Exp i) (Decl i) (Pat i) (Type i)) newtype Decl i = Decl (D (Exp i) (Decl i) (Pat i) (Type i)) newtype Pat i = ... newtype Type i = ...
This required the use of multi-parameter classes. The fact that the type of the abstract syntax uniquely determines the type of identifiers is captured with a functional dependency. The number of degrees of freedom thus remains the same, and we avoid introducing type ambiguities.class Free i s | s->i where free :: s -> [i]instance Free i (Exp i) where free (Exp e) = free e instance (Free i e,Free i d, ...) => Free i (E e d p t) where free = ... ...
Again, this refactoring step was implemented manually. It would probably take a rather sophisticated refactoring tool to automate program transformations of this kind...