2009-03-17 template haskell

First stab at Template Haskell

Template Haskell is a nice extension to Haskell (but only available in GHC as for now). It is reminiscent of the macro mechanism as found in Scheme or Lisp. While in Lisp the concrete syntax and the representation of the code, i.e. the abstract syntax tree, have no difference, the abstract syntax tree for TH is represented with normal Haskell data types.

The documentation of the package is quite terse but ghci is a nice tool to get started. Start ghci with TH enabled, and load the AST data types :

> ghci -XTemplateHaskell
ghci> :m + Language.Haskell.TH

To see what is the data structure needed by TH to generate the code \x -> 1, we can run

ghci> runQ [| \x -> 1 |]
LamE [VarP x_0] (LitE (IntegerL 1))

The [| |] are called Oxford brackets and make it easy to get the AST of some Haskell code (E stands for expression and P for pattern).

We can see a function is a LamE object with some patterns (here, just one variable) and a representation of an expression, the result of the function (here, the literal 1).

ghci> :t it
it :: Exp

(it is a special variable in ghci that allows us to reference the last computed value.) So we have an Exp, representing the Haskell code \x -> 1.

So how can we generate back the code from the Exp ? The answer lies in another special syntax, $(...). The ... has to be of type ExpQ, an alias for the type Q Exp. Q is the quotation monad which makes possible, in particular, to draw unique identifiers (that's why there is a 0 appended to the initial name x, in VarP x_0 above) and we can extract the resulting Exp by using runQ.

ghci> $(return it)
    No instance for (Show (t1 -> t))
      arising from a use of `print' at <interactive>:1:0-11
    Possible fix: add an instance declaration for (Show (t1 -> t))
    In a stmt of a 'do' expression: print it

The error message we get from ghci is precisely the same we would get if we'd typed directly

ghci> \x -> 1

With something that can be actually printed :

ghci> runQ [| 1 + 2 |]
InfixE (Just (LitE (IntegerL 1))) (VarE GHC.Num.+) (Just (LitE (IntegerL 2)))
ghci> $(return it)

Note the possibility to splice (i.e. use $(...)) inside a quotation :

ghci> runQ [| 1 + $([| 2 |]) |]
InfixE (Just (LitE (IntegerL 1))) (VarE GHC.Num.+) (Just (LitE (IntegerL 2)))

One more thing : the quotation we used was to create expressions and has type Q Exp but there are also quotations for type signatures and declarations, which have respectively the type Q Typ and Q [Dec].

ghci> runQ [t| IO Int |]
AppT (ConT GHC.IOBase.IO) (ConT GHC.Types.Int)

ghci> runQ [d| f a = 1 |]
[FunD f [Clause [VarP a_4] (NormalB (LitE (IntegerL 1))) []]]

Now, if you need to generate some Haskell code, you can write, in Haskell, a function which produces a TH AST and then splice it. Beware that you can't write a splice involving a function defined in the same file; you have to import that function to use it. If it happens, you'll simply get a 'stage restriction' error. Have fun with Haskell-generating Haskell.

submit to reddit