Template Haskell

From HaskellWiki
Revision as of 15:50, 14 April 2006 by JaredUpdike (talk | contribs) (Content from old TemplateHaskell hawiki entry)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Template Haskell is a GlasgowHaskellCompiler (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.


Template Haskell Resources

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 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
  • 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 - 1st April 2006 AndyGill

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)) []]

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