Why macros? DRY, DSLs, and performance
Motivation | 6-minute read
- Eliminate boilerplate, embed domain-specific languages, and generate specialized code.
- Trade readability and tooling complexity for zero-cost abstractions.
- Prefer libraries when possible; use macros for pattern-like code.
Textual macros (C/C++)
Preprocessor | 7-minute read
- Token-level substitution; no syntax awareness; pitfalls with precedence and side effects.
- Use inline functions/enums where possible; macros for headers guards and feature flags.
- Variadic macros and stringification help generate code.
#define MIN(a,b) ((a) < (b) ? (a) : (b))
Hygienic macros (Scheme/Racket)
Hygiene | 9-minute read
- Hygiene prevents accidental name capture; macros expand with lexical scope preserved.
- Pattern-based systems (syntax-rules) and procedural systems (syntax-case).
- Macro expansion is a compilation phase with its own environment.
(define-syntax when
(syntax-rules ()
((when test body ...)
(if test (begin body ...)))))
Syntax trees and quote/splice
ASTs | 8-minute read
- Quote code as data; splice to assemble ASTs (Lisp, Scala, MetaOCaml).
- Quasiquotes balance readability and metaprogramming power.
- Staged programming enables multi-phase specialization.
q"{ val x = 1 + 2 }" // Scala quasiquote
Procedural/derive macros (Rust), attributes
Procedural | 8-minute read
- Procedural macros transform token streams to tokens; derive expands traits.
- Attributes attach behavior to items (e.g., serialization, test discovery).
- Use span and hygiene APIs to keep error messages precise.
#[derive(Serialize, Deserialize)]
struct Point { x: i32, y: i32 }
Exercises
Hands-on | 8-minute read
- Write a hygienic macro that desugars a small language construct (e.g., when/guard).
- Implement a Rust procedural macro that derives Display for a struct.
- Use templates or generics to specialize a function for fixed sizes at compile time.
Metaprogramming is powerful—use it to encode patterns, not to hide complexity.