Difference between revisions of "Template Haskell"
m |
m (Change category "Language" to subcategory "Language extensions") |
||
Line 379: | Line 379: | ||
The message [http://www.haskell.org/pipermail/template-haskell/2006-August/000581.html email message] contains the full source ([http://www.iist.unu.edu/~vs/haskell/TH_render.hs extracted file]). |
The message [http://www.haskell.org/pipermail/template-haskell/2006-August/000581.html email message] contains the full source ([http://www.iist.unu.edu/~vs/haskell/TH_render.hs extracted file]). |
||
− | [[Category:Language]] |
+ | [[Category:Language extensions]] |
Revision as of 14:55, 23 June 2007
Template Haskell is a GHC extension to Haskell that adds compile-time metaprogramming facilities. The original design can be found here: http://research.microsoft.com/~simonpj/papers/meta-haskell/ . It is included in GHC version 6. If you start working with Template Haskell you'll probably want to join the Template Haskell mailing list.
This page hopes to be a more central and organized repository of TH related things. However, at this point most things should probably go to/through the mailing list first.
What is Template Haskell?
Template Haskell is an extension to Haskell 98 that allows you to do type-safe compile-time meta-programming, with Haskell both as the manipulating language and the language being manipulated.
Intuitively Template Haskell provides new language features that allow us to convert back and forth between concrete syntax, i.e. what you would type when you write normal Haskell code, and abstract syntax trees. These abstract syntax trees are represented using Haskell datatypes and, at compile time, they can be manipulated by Haskell code. This allows you to reify (convert from concrete syntax to an abstract syntax tree) some code, transform it and splice it back in (convert back again), or even to produce completely new code and splice that in, while the compiler is compiling your module.
There is a mailing list specifically for Template Haskell. It's worth joining if you start to use TH.
Template Haskell specification
Template Haskell is only documented rather informally at the moment. Here are the main resources:
- The user manual section
- The original Template Haskell paper
- Notes on Template Haskell version 2, which describes changes since the original paper.
- The Template Haskell API
Template Haskell tutorials
- A Template Haskell tutorial: http://www.haskell.org/hawiki/TemplateHaskellTutorial
- Bulat's tutorials:
- One reader said "These docs are *brilliant* ! Exactly what I need to get an understanding of TH."
- Mark Snyder's Template Haskell chapter on the Software Generation and Configuration Report
Other useful resources
- Papers about Template Haskell. Would someone like to move this material into the HaskellWiki?
- The old Template Haskell web page. Would someone feel like moving this material into the HaskellWiki?
- Old and probably not too useful for most but maybe... http://www.cse.unsw.edu.au/~chak/haskell/ghc/comm/exts/th.html
Feel free to update our Wikipedia entry http://en.wikipedia.org/wiki/Template_Haskell
Projects
What are you doing/planning to do/have done with Template Haskell?
- I have written a primitive (untyped) binding to the Objective-C runtime system on Mac OS X. It needs just TH, no "stub files" are created, no seperate utilities are required. Initial snapshot is at http://www.kfunigraz.ac.at/imawww/thaller/wolfgang/HOC020103.tar.bz2 -- WolfgangThaller
- I am writing Template Greencard - a reimplementation of GreenCard using TH. Many bits work out really nicely. A few bits didn't work so nicely - once I get some time to think, I'll try to persuade the TH folk to make some changes to fix some of these. -- AlastairReid
- I'm writing Hacanon - a framework for automatic generation of C++ bindings. Read "automated Template Greencard for C++" (-: Darcs repo: http://www.ScannedInAvian.org/repos/hacanon - You'll need gccxml (http://www.gccxml.org/) to compile the exmples. - 27 Dec Lemmih.
- Following other FFI tools developers, I see some future for Template HSFFIG, especially when it comes to autogenerate peek and poke methods for structures defined in C; may be useful for implementation of certain network protocols such as X11 where layout of messages is provided as C structure/union declaration. - 16 Dec 2005 DimitryGolubovsky
- I am using Template Haskell as a mechanism to get parsed, typechecked code into an Ajax based Haskell Equational Reasoning tool, as well as simplify the specification of equational relationships between pieces of code. There is quicktime movie of the tool being used on http://www.gill-warbington.com/home/andy/share/hera1.html - AndyGill
- I am working on functional metaprogramming techniques to enhance programming reliability and productivity, by reusing much of the existing compiler technology. Template Haskell is especially interesting for me because it permits to check size information of structures by the compiler, provided this information is available at compile time. This approach is especially appropriate for hardware designs, where the structures are fixed before the circuit starts operating. See our metaprogramming web page at http://www.infosun.fmi.uni-passau.de/cl/metaprog/ -- ChristophHerrmann(http://www.cs.st-and.ac.uk/~ch)
Utilities
Helper functions, debugging functions, or more involved code e.g. a monadic fold algebra for THSyntax.
Known Bugs
Take a look at the open bugs against Template Haskell on the GHC bug tracker.
The bug you're most likely to run into is Template Haskell does not work with profiling.
Wish list
Any other features that may be nice, and TH projects you'd want to see.
- A TH tutorial (mainly a distillation and update of Template Meta-programming in Haskell at this point)
- Write Haddock documentation for the Template Haskell library.
- Make `reify` on a class return a list of the instances of that class (http://www.haskell.org/pipermail/template-haskell/2005-December/000503.html).
- A set of simple examples on this wiki page
- A TH T-shirt with new logo to wear at conferences
- (Long-term) Unify Language.Haskell.Syntax with Language.Haskell.TH.Syntax so there's just one way to do things
Tips and Tricks
What to do when you can't splice that there
When you try to splice something into the middle of a template and find that you just can't, instead of getting frustrated about it, why not use the template to see what it would look like in longhand?
First, an excerpt from a module of my own. I, by the way, am SamB.
{-# OPTIONS_GHC -fglasgow-exts -fth #-}
module MMixMemory where
import Data.Int
import Data.Word
class (Integral int, Integral word)
=> SignConversion int word | int -> word, word -> int where
toSigned :: word -> int
toSigned = fromIntegral
toUnsigned :: int -> word
toUnsigned = fromIntegral
Say I want to find out what I need to do to splice in the types for an instance declaration for the SignConversion class, so that I can declare instances for Int8 with Word8 through Int64 with Word64. So, I start up good-ol' GHCi and do the following:
$ ghci -fth -fglasgow-exts
Prelude> :l MMixMemory
*MMixMemory> :m +Language.Haskell.TH.Syntax
*MMixMemory Language.Haskell.TH.Syntax> runQ [d| instance SignConversion Int Word where |] >>= print
[InstanceD [] (AppT (AppT (ConT MMixMemory.SignConversion) (ConT GHC.Base.Int)) (ConT GHC.Word.Word)) []]
Why does runQ crash if I try to reify something?
This program will fail with an error message when you run it:
main = do info <- runQ (reify (mkName "Bool"))
putStrLn (pprint info)
Reason: reify consults the type environment, and that is not available at run-time. The type of reify is
reify :: Quasi m => Q a -> m a
The IO monad is a poor-man's instance of Quasi; it can allocate unique names and gather error messages, but it can't do reify. This error should really be caught statically.
Here's an email thread with more details.
Examples
Select from a tuple
An example to select an element from a tuple of arbitrary size. Taken from this paper.
Use like so:
> $(sel 2 3) ('a','b','c') 'b' > $(sel 3 4) ('a','b','c','d') 'c'
sel :: Int -> Int -> ExpQ
sel i n = [| \x -> $(caseE [| x |] [alt]) |]
where alt :: MatchQ
alt = match pat (normalB rhs) []
pat :: Pat
pat = tupP (map varP as)
rhs :: ExpQ
rhs = varE(as !! (i -1)) -- !! is 0 based
as :: [String]
as = ["a" ++ show i | i <- [1..n] ]
Alternately:
sel' i n = lamE [pat] rhs
where pat = tupP (map varP as)
rhs = varE (as !! (i - 1))
as = [ "a" ++ show j | j <- [1..n] ]
Convert the first n elements of a list to a tuple
This example creates a tuple by extracting elemnts from a list. Taken from www.xoltar.org
Use like so:
> $(tuple 3) [1,2,3,4,5] (1,2,3) > $(tuple 2) [1,2] (1,2)
tuple :: Int -> ExpQ
tuple n = [|\list -> $(tupE (exprs [|list|])) |]
where
exprs list = [infixE (Just (list))
(varE "!!")
(Just (litE $ integerL (toInteger num)))
| num <- [0..(n - 1)]]
Printf
This example taken from: http://haskell.cs.yale.edu/ghc/docs/6.0/html/users_guide/template-haskell.html
Build it using a command similar to one of the following (depending on your environment):
ghc/compiler/stage3/ghc-inplace --make -fglasgow-exts -package haskell-src main.hs -o main.exe ghc --make -fth Main.hs -o printfTest
Main.hs:
module Main where
-- Import our template "pr"
import Printf ( pr )
-- The splice operator $ takes the Haskell source code
-- generated at compile time by "pr" and splices it into
-- the argument of "putStrLn".
main = putStrLn ( $(pr "Hello") )
Printf.hs:
module Printf where
-- Skeletal printf from the paper.
-- It needs to be in a separate module to the one where
-- you intend to use it.
-- Import some Template Haskell syntax
import Language.Haskell.THSyntax
-- Describe a format string
data Format = D | S | L String
-- Parse a format string. This is left largely to you
-- as we are here interested in building our first ever
-- Template Haskell program and not in building printf.
parse :: String -> [Format]
parse s = [ L s ]
-- Generate Haskell source code from a parsed representation
-- of the format string. This code will be spliced into
-- the module which calls "pr", at compile time.
gen :: [Format] -> ExpQ
gen [D] = [| \n -> show n |]
gen [S] = [| \s -> s |]
gen [L s] = stringE s
-- Here we generate the Haskell code for the splice
-- from an input format string.
pr :: String -> ExpQ
pr s = gen (parse s)
Handling Options with Templates
A common idiom for treating a set of options, e.g. from GetOpt, is to define a datatype with all the flags and using a list over this datatype:
data Options = B1 | B2 | V Integer
options = [B1, V 3]
While it's simple testing if a Boolean flag is set (simply use "elem"), it's harder to check if an option with an argument is set. It's even more tedious writing helper-functions to obtain the value from such an option since you have to explicitely "un-V" each. Here, Template Haskell can be (ab)used to reduce this a bit. The following example provides the module "OptionsTH" which can be reused regardless of the constructors in "Options". Let's start with showing how we'd like to be able to program. Notice that the resulting lists need some more treatment e.g. through "foldl".
Options.hs:
module Main where
import OptionsTH
import Language.Haskell.THSyntax
data Options = B1 | B2 | V Int | S String deriving (Eq, Read, Show)
options = [B1, V 3]
main = do
print foo -- test if B1 set: [True,False]
print bar -- test if V present, w/o value: [False,True]
print baz -- get value of V if available: [Nothing,Just 3]
foo :: [Bool]
-- Query constructor B1 which takes no arguments
foo = map $(getopt (THNoArg (mkArg "B1" 0))) options
bar :: [Bool]
-- V is a unary constructor. Let mkArg generate the required
-- wildcard-pattern "V _".
bar = map $(getopt (THNoArg (mkArg "V" 1))) options
-- Can't use a wildcard here!
baz :: [(Maybe Int)]
baz = map $(getopt (THArg (conP "V" [varP "x"]))) options
OptionsTH.hs
module OptionsTH where
import Language.Haskell.THSyntax
-- datatype for querying options:
-- NoArg: Not interested in value (also applies to Boolean flags)
-- Arg: Grep value of unary(!) constructor
data Args = THNoArg Pat | THArg Pat
getopt :: Args -> ExpQ
getopt (THNoArg pat) = lamE [varP "y"] (caseE (varE "y") [cons0, cons1])
where
cons0 = match pat (normalB [| True |]) []
cons1 = match wildP (normalB [| False |]) []
-- bind "var" for later use!
getopt (THArg pat@(ConP _ [VarP var])) = lamE [varP "y"] (caseE (varE "y") [cons0, cons1])
where
cons0 = match pat (normalB (appE [|Just|] (varE var))) []
cons1 = match wildP (normalB [|Nothing|]) []
mkArg :: String -> Int -> Pat
mkArg k c = conP k (replicate c wildP)
While the example might look contrived for the Boolean options which could have been tested much easier, it shows how both types of arguments can be treated in a similar way.
Limitations
getopt (THArg pat) is only able to treat unary constructors. See the pattern-binding: It matches exactly a single VarP.
Improvements
The following reduces things even a bit more, though I still don't know if I like it. It only works since c is either 0 or 1.
mkArg k c = conP k (replicate c (varP "x"))
baz = map $(getopt (THArg (mkArg "V" 1)))
-- VolkerStolz
Generic constructor for records
I have a large number of record types like this, of different length:
data PGD = PGD {
pgdXUnitBase :: !Word8,
pgdYUnitBase :: !Word8,
pgdXLUnitsperUnitBase :: !Word16
}
Currently I use GHC's Binary module to read them from files; it can handle types like (Word8, (Word8, Word16)), but there was no easy way to generate the correct amount of "uncurry" calls for automatically grabbing each element.
With Template Haskell, the instance declarations are now written as such:
instance Binary PGD where
get bh = do a <- get bh ; return $ $(constrRecord PGD) a
Here the trick lies in constrRecord, which is defined as:
constrRecord x = reify exp where
reify = \(Just r) -> appE r $ conE $ last args
exp = foldl (dot) uncur $ replicate terms uncur
terms = ((length args) `div` 2) - 2
dot x y = (Just $ infixE x (varE ".") y)
uncur = (Just [|uncurry|])
args = words . show $ typeOf x
-- AutrijusTang
Instance deriving example
An example using a 'deriving function' to generate a method instance per constructor of a type. The deriving function provides the body of the method.
Note that this example assumes that the functions of the class take a parameter that is the same type as instance is parameterized with.
The message email message contains the full source (extracted file).