Skip to content

🧩 1. Template Haskell Deep Dive: A Comprehensive Guide to Compile‐Time Metaprogramming

Bernard Sibanda edited this page Oct 25, 2025 · 1 revision

📘 Table of Contents

  1. 🧠 Introduction
  2. ⚙️ Template Haskell Overview
  3. 🧩 The Q Monad and Code Generation
  4. 🌳 The Abstract Syntax Tree (AST) of Haskell
  5. 🔢 Expression-Level AST (Exp)
  6. 🎯 Pattern-Level AST (Pat)
  7. 🧱 Declaration-Level AST (Dec)
  8. 🧮 Literal Nodes (Lit)
  9. 🔁 Lambda Expressions (LamE) and Function Application (AppE)
  10. 🧰 Type-Level AST (Type)
  11. 🧭 Typeclasses and Instances (ClassD, InstanceD)
  12. 🏗️ Data Types and Constructors (DataD, RecC, NormalC)
  13. 📜 Quasiquotation and Splicing
  14. 🧪 Inspecting ASTs in GHCi (runQ, pprint)
  15. 🧠 Glossary of Key Terms
  16. 📚 Recommended Further Reading

1. 🧠 Introduction

Template Haskell (TH) is Haskell’s meta-programming system — it lets you write code that writes code. It allows compile-time generation, inspection, and transformation of Haskell programs.

In other words:

Template Haskell gives you direct access to Haskell’s Abstract Syntax Tree (AST) — allowing manipulation of code as data.

This is the backbone of systems like Plutus, Aeson’s deriveJSON, and lens auto-generation.

2. ⚙️ Template Haskell Overview

Template Haskell extends the compiler with:

  • Quotation: converting code into data ([| ... |], [t| ... |], etc.)
  • Splicing: inserting generated code into your program ($( ... ))
  • Meta-programming API: constructing and analyzing syntax trees directly

For example:

{-# LANGUAGE TemplateHaskell #-}

module Example where
import Language.Haskell.TH

makeHello :: String -> Q [Dec]
makeHello name = [d| greet = putStrLn ("Hello, " ++ name) |]

Splicing this with:

$(makeHello "Haskell")

Generates a new top-level declaration:

greet = putStrLn ("Hello, Haskell")

3. 🧩 The Q Monad and Code Generation

All Template Haskell computations occur inside the Q monad — a special compile-time context.

Type Purpose
Q Exp Generate expressions
Q [Dec] Generate declarations
Q Type Generate type expressions
Q Pat Generate patterns

Think of Q like IO, but instead of side effects, it builds syntax trees during compilation.

Example:

makeConst :: Integer -> Q Exp
makeConst n = [| n + 1 |]

4. 🌳 The Abstract Syntax Tree (AST)

Template Haskell treats code as structured data using these main types:

Category Type Description
Expressions Exp Represents code that does things
Patterns Pat Used in function definitions and matches
Declarations Dec Top-level definitions (functions, data, instances)
Types Type Describes Haskell type expressions
Literals Lit Constants (numbers, strings, etc.)

Each is a recursively defined tree of constructors.

5. 🔢 Expression-Level AST (Exp)

Expressions represent code that runs — function calls, variables, numbers, lambdas, etc.

Constructor Meaning Example
VarE Variable VarE 'xx
LitE Literal LitE (IntegerL 42)42
AppE Application AppE (VarE 'f) (VarE 'x)f x
InfixE Infix expression InfixE (Just a) (VarE '(+)) (Just b)a + b
LamE Lambda LamE [VarP 'x] (VarE 'x)\x -> x
ConE Constructor ConE 'JustJust

Example:

-- Haskell
f x = x + 1

-- Template Haskell
FunD (mkName "f")
  [ Clause [VarP (mkName "x")]
           (NormalB (InfixE (Just (VarE (mkName "x")))
                            (VarE '(+))
                            (Just (LitE (IntegerL 1)))))
           []
  ]

6. 🎯 Pattern-Level AST (Pat)

Used in function arguments and pattern matching.

Constructor Example Represents
VarP VarP 'x Variable pattern
LitP LitP (IntegerL 5) Literal pattern
ConP ConP 'Just [VarP 'x] Constructor pattern (Just x)
TupP TupP [VarP 'a', VarP 'b'] Tuple pattern

7. 🧱 Declaration-Level AST (Dec)

Represents top-level items like functions, data, instances, and classes.

Constructor Purpose
FunD Function declaration
ValD Value binding
DataD, NewtypeD Type constructors
ClassD, InstanceD Typeclasses and instances
TySynD Type synonym
SigD Type signature

Example:

FunD (mkName "square")
  [ Clause [VarP (mkName "x")]
           (NormalB (InfixE (Just (VarE 'x)) (VarE '(*)) (Just (VarE 'x))))
           []
  ]

8. 🧮 Literal Nodes (Lit)

Literal Example TH Representation
Integer 42 LitE (IntegerL 42)
String "Hi" LitE (StringL "Hi")
Char 'a' LitE (CharL 'a')
Float 3.14 LitE (RationalL 3.14)

9. 🔁 Lambda (LamE) and Application (AppE)

Example:

\x -> f (g x)

TH Representation:

LamE [VarP (mkName "x")]
     (AppE (VarE 'f)
           (AppE (VarE 'g) (VarE 'x)))

Tree:

LamE [x]
└── AppE
    ├── VarE "f"
    └── AppE
        ├── VarE "g"
        └── VarE "x"

10. 🧰 Type-Level AST (Type)

Type Expression TH Representation
Int ConT ''Int
a VarT (mkName "a")
Maybe Int AppT (ConT ''Maybe) (ConT ''Int)
a -> b AppT (AppT ArrowT (VarT "a")) (VarT "b")
[Int] AppT ListT (ConT ''Int)
(a, b) AppT (AppT (TupleT 2) (VarT "a")) (VarT "b")
forall a. a -> a ForallT [PlainTV "a"] [] (AppT (AppT ArrowT (VarT "a")) (VarT "a"))

11. 🧭 Typeclasses and Instances

Example 1 — Class Declaration

class Eq a where (==) :: a -> a -> Bool

TH:

ClassD [] (mkName "Eq") [PlainTV (mkName "a")] []
  [SigD (mkName "==")
        (AppT (AppT ArrowT (VarT "a"))
              (AppT (AppT ArrowT (VarT "a")) (ConT ''Bool)))]

Example 2 — Instance Declaration

instance Eq Int where (==) = (Prelude.==)

TH:

InstanceD Nothing [] (AppT (ConT ''Eq) (ConT ''Int))
  [FunD (mkName "==") [Clause [] (NormalB (VarE '(Prelude.==))) []]]

12. 🏗️ Data Types and Constructors

Example 1 — Sum Type

data Maybe a = Nothing | Just a

TH:

DataD [] (mkName "Maybe") [PlainTV (mkName "a")] Nothing
  [ NormalC (mkName "Nothing") []
  , NormalC (mkName "Just")
            [(Bang NoSourceUnpackedness NoSourceStrictness, VarT "a")]
  ] []

Example 2 — Record

data Person = Person { name :: String, age :: Int }

TH:

RecC (mkName "Person")
  [ (mkName "name", Bang NoSourceUnpackedness NoSourceStrictness, ConT ''String)
  , (mkName "age", Bang NoSourceUnpackedness NoSourceStrictness, ConT ''Int)
  ]

13. 📜 Quasiquotation and Splicing

Symbol Meaning
`[ ... ]` Expression quote (Q Exp)
`[d ... ]` Declaration quote (Q [Dec])
`[t ... ]` Type quote (Q Type)
`[p ... ]` Pattern quote (Q Pat)
$( ... ) Splice — inject generated code

Example:

square x = $( [| x * x |] )

14. 🧪 Inspecting ASTs in GHCi

You can explore TH results interactively:

{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH

runQ [d| data Maybe a = Nothing | Just a |]
runQ [t| Maybe Int |]
runQ [| \x -> x + 1 |]

To print a readable version:

runQ [d| f x = x + 1 |] >>= putStrLn . pprint

15. 🧠 Glossary of Key Terms

Term Meaning
AST (Abstract Syntax Tree) Structural representation of code used by the compiler
Meta-programming Writing programs that generate or manipulate other programs
**Quotation `[ ... ]`** Captures code as syntax data instead of evaluating it
Splicing $(...) Inserts generated code into the program
Q Monad Compile-time context where TH computations run
Name Identifier in Template Haskell
mkName Creates a Name from a string
VarE, ConE Variable and constructor expression nodes
AppE / AppT Function and type application
LamE Lambda expression
DataD, ClassD, InstanceD Declarations of types, classes, and instances
Bang Field strictness annotation in records
DerivClause Representation of deriving (...)

16. 📚 Further Reading and References

📘 Official Documentation

📗 Tutorials & Guides

📙 Related Topics

  • Quasiquotation System — embedding custom syntax
  • PlutusTx — Template Haskell used in Cardano smart contracts
  • GHC API — advanced compiler manipulation layer

🧭 Final Words

Template Haskell unlocks the power of code generation, static guarantees, and metaprogramming within Haskell’s type system. It’s the engine behind many libraries and DSLs — from Aeson’s deriveJSON to on-chain validators in Cardano.

Mastering it means:

  • You understand Haskell’s compiler internals at the syntax-tree level.
  • You can make the compiler generate large parts of your code automatically.

“Code that writes code is the most powerful kind of code.”

Clone this wiki locally