-
Notifications
You must be signed in to change notification settings - Fork 4
🧩 1. Template Haskell Deep Dive: A Comprehensive Guide to Compile‐Time Metaprogramming
- 🧠 Introduction
- ⚙️ Template Haskell Overview
- 🧩 The
QMonad and Code Generation - 🌳 The Abstract Syntax Tree (AST) of Haskell
- 🔢 Expression-Level AST (
Exp) - 🎯 Pattern-Level AST (
Pat) - 🧱 Declaration-Level AST (
Dec) - 🧮 Literal Nodes (
Lit) - 🔁 Lambda Expressions (
LamE) and Function Application (AppE) - 🧰 Type-Level AST (
Type) - 🧭 Typeclasses and Instances (
ClassD,InstanceD) - 🏗️ Data Types and Constructors (
DataD,RecC,NormalC) - 📜 Quasiquotation and Splicing
- 🧪 Inspecting ASTs in GHCi (
runQ,pprint) - 🧠 Glossary of Key Terms
- 📚 Recommended Further Reading
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.
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")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 |]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.
Expressions represent code that runs — function calls, variables, numbers, lambdas, etc.
| Constructor | Meaning | Example |
|---|---|---|
VarE |
Variable |
VarE 'x → x
|
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 'Just → Just
|
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)))))
[]
]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 |
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))))
[]
]| 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) |
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"
| 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")) |
class Eq a where (==) :: a -> a -> BoolTH:
ClassD [] (mkName "Eq") [PlainTV (mkName "a")] []
[SigD (mkName "==")
(AppT (AppT ArrowT (VarT "a"))
(AppT (AppT ArrowT (VarT "a")) (ConT ''Bool)))]instance Eq Int where (==) = (Prelude.==)TH:
InstanceD Nothing [] (AppT (ConT ''Eq) (ConT ''Int))
[FunD (mkName "==") [Clause [] (NormalB (VarE '(Prelude.==))) []]]data Maybe a = Nothing | Just aTH:
DataD [] (mkName "Maybe") [PlainTV (mkName "a")] Nothing
[ NormalC (mkName "Nothing") []
, NormalC (mkName "Just")
[(Bang NoSourceUnpackedness NoSourceStrictness, VarT "a")]
] []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)
]| 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 |] )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| 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 (...)
|
-
GHC User Guide — Template Haskell 🔗 https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/exts/template_haskell.html
-
Hackage: template-haskell Library 🔗 https://hackage.haskell.org/package/template-haskell
- Template Haskell Overview — Mark Lentczner 🔗 https://wiki.haskell.org/Template_Haskell
- Meta-programming in Haskell — Chris Done 🔗 https://chrisdone.com/posts/template-haskell/
- Metaprogramming in Practice — Plutus Pioneer Program Lecture Notes (IOG / Cardano Developer Portal)
- Quasiquotation System — embedding custom syntax
- PlutusTx — Template Haskell used in Cardano smart contracts
- GHC API — advanced compiler manipulation layer
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.”
Bernard Sibanda is a global Technology Entrepreneur, Web3 and Software Consultant with a deep focus on Cardano Blockchain, Midnight and Community building.
Key Positions:
- Founder, CTO, Developer Advocate cohort #1, Fullstake Developer, Cardano Ambassador, Catalyst Project Manager, DREP-WIMS:
- Co-founder of ABL Tech and Cardano Africa Live
- EBU-certified Plutus Pioneer (Plutus/Haskell)
- Cohort #1 Plutus Pioneer Developer
- Catalyst Community Reviewer & Funded Projects Manager
-
DRep for WIMS-Cardano (ID:
drep1yguj8zu48n99pv70yl6ckzt9hdgjy8yjnlqs2uyzcpafnjgu4vkul) - Intersect Developer Advocate
- Intersect Committe Member 2025-2026
- Cardano Marketer,Promoter and blogger
- Cardano Open Source Contributor
- Cardano communities and events organizer and builder
- Cardano Ambassador for South Africa
Official links:
- Stablecoins Dex
- Coxygen Global Universities
- WIMS Cardano Global
- Cardano Africa Live
- WIMS Cardano Videos
- Cardano Smart Contract Videos
- Fullstack IT Consulting
Social links: