[Haskell-cafe] Is Template Haskell a suitable macro language?

Robin Green greenrd at greenrd.org
Tue Apr 24 10:36:28 EDT 2007


On Tue, 24 Apr 2007 14:23:47 +0100
Joel Reymont <joelr1 at gmail.com> wrote:

> 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


More information about the Haskell-Cafe mailing list