
On Tue, 24 Apr 2007 14:23:47 +0100
Joel Reymont
I'm finding myself dealing with several large abstract syntax trees that are very similar in nature. The constructor names would be the same or one type may be a small extension of another.
This is something that I wouldn't worry about with Lisp, for example, as I would create a bunch of macros for creating syntax trees and reuse them all over. I cannot do this in Haskell, though, as my "macros" are functions and so I must repeat them for every AST since they return different types.
I'm wondering if Template Haskell is a suitable replacement for Lisp macros.
What is the consensus?
I saw your earlier post on abstract syntax trees, and I have indeed been using Template Haskell for processing syntax trees. (Sorry I didn't reply earlier.) It works quite well, yes. Here's my success story. Basically I have two modules, Abstract and Concrete, which define data types for abstract and concrete syntax respectively. The Abstract module also contains code to convert concrete syntax (i.e. what comes out of the parser) into abstract syntax, and the code that just does copying (i.e. the boilerplate code) is generated by Template Haskell code. What I do looks like this: $(let preprocess :: [Dec] -> Q [Dec] -- definition omitted in preprocess =<< [d|type Param = (Ident, Term) data FixBody = FixBody Ident [Param] (Maybe Annotation) (Maybe Term) Term deriving (Typeable, Data, Eq) data MatchItem = MatchItem Term (Maybe Ident) (Maybe Term) deriving (Typeable, Data, Eq) data IdentWithParams = IdentWithParams Ident [Param] (Maybe Term) deriving (Typeable, Data, Eq) class Abstraction c a {- | c -> a -} where { abstractL :: Monad m => c -> StateT [(Ident,Term)] m a } #include "common2.inc" |]) Let me explain what's going on here, starting from the bottom. The order of these parts is very important! The #include "common2.inc" includes the type definitions which are common to both modules. (I actually maintain a file "common.inc" and then that is preprocessed to replace newlines with semicolons, in order to avoid the problem that would otherwise occur that the file would be included at the wrong identation level. Although the file is still included at the wrong indentation level, apparently the use of semicolons mollifies ghc!) The reason why I don't just put the stuff in common2.inc into another module is because it refers to types that are defined *differently* in each syntax! So I really am using cpp [actually, cpphs] for an appropriate purpose here. The class Abstraction defines a method abstractL. It is defined for every concrete syntax data type and specifies how to translate that type into abstract syntax (except the top-level which is handled differently). The fundep is commented out because (a) ghc rejected it and (b) I didn't need it anyway. The preprocess function then takes all of the decls between [d| and |] as input, passes through the declarations before the class declaration (i.e. the ones that are not the same) without looking at them any further, and then generates instances of the class for each data declaration given below the class (i.e. all the types which are in common, where only automatic copying code needs to be generated). It also passes through all of the declarations given as input. Why not simply put the initial declarations at the top of the file? Well, ghc rejects that, if I remember correctly, because they refer to other types which have yet to be generated (well, passed through) by Template Haskell. Template Haskell seems to break the general principle in Haskell that one can refer to a declaration in the same module, textually before that declaration. -- Robin