Templatehaskell
Here are some notes on using Template Haskell. Note that the Template Haskell API is still in flux and this may no longer be relevent if the API changes.
Read the original research paper for preparation.
I like to use a shortcut to easily load Template Haskell modules. In the ~/.ghci file, ensure that the following line exist.
:def . readFile
This will let you load often used ghci commands from a command file. Next, create a file th.cmd with the following content
:m +Language.Haskell.TH
:m +Language.Haskell.TH.Syntax
:m +Language.Haskell.TH.Lib
The th.cmd can be loaded into your ghci session as below:
ghci> :. th.cmd
The notes are in literate Haskell format. You will need the following initial imports at the beginning.
{-# LANGUAGE TemplateHaskell, LANGUAGE QuasiQuotes #-}
> module Main where
> import Language.Haskell.TH
> import Language.Haskell.TH.Syntax
> import Language.Haskell.TH.Lib
> -- import Defs -- to be enabled
> -- import Str -- later
Syntax
A template haskell expression can be executed by marking it with $(...)
that
is, if you have an function myfn
, then $myfn
would cause it to be called
at compile time with no arguments. and $(myfn a) would cause it to be called
with argument a, with the result added to AST before being compiled.
The template haskell expression is the AST of haskell expression to be spliced in. For example, the following expression
$(return $ LitE $ IntegerL 1 :: Q Exp)
should return 1
.
The $(...)
syntax expects a Q
monad. Hence we wrapped the Q Exp
in a
return
to make it palatable to the monad.
Sicne it is tedious to construct the haskell AST by hand, Template haskell provides the following shortcuts.
Patterns are constructed with, [p| ... |]
with type Q Pat
. For example:
runQ [p| x |]
Global declarations use [d| ... |]
, and has type Q [Dec]
. For example:
runQ [d| x = 100 |]
Types are constructed with [t| ... |]
, with Q Type
. For example:
runQ [t| Int |]
To extract the AST of haskell expressions, we use [| ... |]
, with type Q Exp
runQ [| 100 |]
The cancellation requirement
Remember our original expression? we hand coded the values directly for
$(...)
. That expression is equivalent to:
$([|1|])
That is, $([?|..|])
and [?|$(..)|]
are strictly cancellable as per the
meta-haskell paper.
(The paper uses no parenthesis for cancellation. That is $[|1|]
is a valid expression as far
as the meta-haskell paper is concerned. However ghc7 requires the parenthesis for it to work.)
For ghc7, the following cancellation works.
runQ [| $( return $ LitE $ IntegerL 1 ) |]
Bugs?
The patterns [p| ... |]
do not play nice with splicing. To illustrate, follow
this deconstruction.
runQ [| let x = 100 in x |]
runQ [| let x = $(return (LitE (IntegerL 100))) in x |]
runQ [| let x = $(return (LitE (IntegerL 100))) in $(return (VarE (mkName "x"))) |]
runQ [| let $(return (VarP (mkName "x"))) = $(return (LitE (IntegerL 100))) in $(return (VarE (mkName "x"))) |]
Everything except the last one works. I stumbled on it for a long time before asking in the freenode#haskell and according to someone there, patterns do not work well with splicing. Apparently there is a ticket which is patched in GHC Head.
Aside
The reason for the existence of Q monad
is to avoid variable capturing.
This is ensured by a construct newName
which creates a new name to be used
within the template body. On the other hand, we have a capturing variable
construct mkName
which is a pure function.
We can also replace mkName “x” with ‘x
How do we construct these things?
Say we want a way to extract the first value out of any tuple, the expression we want to create is something like
$(fst n) n-tuple
That is, we should be able to evaluate it as follows:
$(fst 3) (1,2,3)
= 3
The expression we want to construct looks like:
let fn (a,b,c) = a in fn
That is, we should be able to use the expression we make in place of the below expression in parenthesis.
ghci> (let fn (a,b,c) = a in fn) (1,2,3)
= 1
We start by examining how tuples are made:
runQ [| (1,2,3) |]
= TupE [LitE (IntegerL 1),LitE (IntegerL 2),LitE (IntegerL 3)]
This suggests that, a tuple is an array of literals wrapped with constructor
LitE
again wrapped in TupE
How about the expression template we demonstrated previously deconstruct?
runQ [| let fn (a,_,_) = a in fn |]
= LetE [FunD fn_0 [Clause [TupP [VarP a_1,WildP,WildP]] (NormalB (VarE a_1)) []]] (VarE fn_0)
So we need to construct our TupP
array.
(We use a lambda here so as to avoid the staging restriction.)
> x = $((\n -> let a = mkName "a" ;
> f = mkName "fn" in
> return (LetE [FunD f [Clause [TupP (VarP a : take (n-1) (repeat WildP))] (NormalB (VarE a)) []]] (VarE f)))
> 3) (10,20,30)
If we use a staging module, Defs.lhs
module Defs where
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Language.Haskell.TH.Lib
nfst n = let a = mkName "a" ; f = mkName "fn" in
return (LetE [FunD f [Clause [TupP (VarP a : take (n-1) (repeat WildP))] (NormalB (VarE a)) []]] (VarE f))
$(nfst 3) (10,20,30)
Can you guess what would happen if we execute
$(nfst 1) (10,20,30)
We can do similar things with types.
runQ [t| Int -> Int |]
Comparison with Scheme macros.
- Lazy evaluation removes some of the requirements for lisp-macro expressions, but is somewhat restricted where it can be used.
- Template Haskell has no dynamic metaprogramming (eval) possibility
- No possibility of syntax-case like DSL for TH. The errors are caught deep inside the template code. (While type system catches a portion of the errors at compile time, it does not do that for all.)
- TH support for patterns and types is still evolving.
- quasiquoting is just sugar for TH
Reification
reify ''Int
This gets you Q Info
$(stringE . show =<< reify ''Int)
> myvar = 5 :: Int
$(stringE . show =<< reify 'myvar)
$(stringE . show =<< reify 'even)
$(stringE . show =<< reify ''String)
Now for quasiquotes.
The first example is on a simple multiline string reader.
module Str where
import Language.Haskell.TH
import Language.Haskell.TH.Quote
qq = QuasiQuoter { quoteExp = (litE . StringL) }
Now, we can use qq to add multiline statements.
> multi = [qq|
> A
> B
> C
> |]
same as $(quoteExp qq “ a b c d “)