,

Contents · Macros and metaprogramming


Why macros? DRY, DSLs, and performance

  • 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++)

  • 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 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

  • 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 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 }

Templates and generics as metaprogramming

  • C++ templates: Turing-complete compile-time computation; SFINAE and concepts.
  • Generics + monomorphization (Rust/Go) vs erasure (Java).
  • Const-eval and constexpr enable partial evaluation.

Build-time vs compile-time vs run-time meta

  • Codegen at build-time (tools), compile-time (macros/templates), or run-time (eval/JIT).
  • Consider reproducibility, caching, and security policies.
  • Prefer earlier phases for performance; later phases for flexibility.

Safety, hygiene, and debuggability

  • Hygienic systems reduce name capture; generate unique symbols when needed.
  • Generate friendly errors; preserve source maps/spans for tooling.
  • Limit macro complexity; document expansion contracts.

Exercises

  1. Write a hygienic macro that desugars a small language construct (e.g., when/guard).
  2. Implement a Rust procedural macro that derives Display for a struct.
  3. 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.