Is Template Haskell a suitable macro language?

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? Thanks, Joel -- http://wagerlabs.com/

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

I have the same problem too when using Haskell. The more I try to enforce static guarantees the more I get lots of datatypes that are similar except for one or two constructors. The best way I have found to avoid this is to simply give up on some of the static guarantees and just use one datatype that contains all the constructors. Less static guarantees but also less needless type coaxing between 90% similar types. I haven't tried using macros. On Tue, 24 Apr 2007, Joel Reymont 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?
Thanks, Joel
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Magnus Jonsson wrote:
I have the same problem too when using Haskell. The more I try to enforce static guarantees the more I get lots of datatypes that are similar except for one or two constructors. The best way I have found to avoid this is to simply give up on some of the static guarantees and just use one datatype that contains all the constructors. Less static guarantees but also less needless type coaxing between 90% similar types. I haven't tried using macros. In Ocaml, you can frequently use polymorphic variants to get the same effect.
Which means that if you are willing to do enough type-class-hackery, it should, in principle, be possible to do the same in Haskell. But it sure isn't as convenient! This all points to some clear need for more ``flavours'' of polymorphism being needed (in Haskell), to be able to express *in the type system* what TH allows you to say "outside". Jacques

On 4/24/07, Jacques Carette
In Ocaml, you can frequently use polymorphic variants to get the same effect.
Which means that if you are willing to do enough type-class-hackery, it should, in principle, be possible to do the same in Haskell. But it sure isn't as convenient!
You seem to imply that there is an encoding of polymorphic variants in Haskell using type classes. While I know that it's possible to achieve similar effects using type classes I haven't seen a direct encoding. If there is such an encoding I would be very interested to hear about it.
This all points to some clear need for more ``flavours'' of polymorphism being needed (in Haskell), to be able to express *in the type system* what TH allows you to say "outside".
I totally agree with this. Cheers, Josef

Josef Svenningsson wrote:
On 4/24/07, Jacques Carette
wrote: In Ocaml, you can frequently use polymorphic variants to get the same effect.
Which means that if you are willing to do enough type-class-hackery, it should, in principle, be possible to do the same in Haskell. But it sure isn't as convenient!
You seem to imply that there is an encoding of polymorphic variants in Haskell using type classes. While I know that it's possible to achieve similar effects using type classes I haven't seen a direct encoding. If there is such an encoding I would be very interested to hear about it. As usual, look for a solution from Oleg: http://www.haskell.org/pipermail/haskell/2006-July/018172.html
There was also a proposal by Koji Kagawa (published at Haskell '06) http://portal.acm.org/citation.cfm?id=1159842.1159848&coll=&dl=ACM&type=series&idx=1159842&part=Proceedings&WantType=Proceedings&title=Haskell&CFID=15151515&CFTOKEN=6184618 Jacques
participants (5)
-
Jacques Carette
-
Joel Reymont
-
Josef Svenningsson
-
Magnus Jonsson
-
Robin Green