Chapter 8: Macros
Macros let you extend the language by transforming code at compile time.
Why Macros?
Consider implementing when:
(when (> x 0)
(display "positive")
(do-something))You can't write this as a procedure because all arguments are evaluated. With a macro, you control when and if arguments are evaluated.
Defining Macros with syntax-rules
(define-syntax when
(syntax-rules ()
((when test body ...)
(if test
(begin body ...)
#f))))This says: When you see (when test body ...), replace it with (if test (begin body ...) #f).
Let's break it down:
define-syntaxcreates a macrosyntax-rules ()defines pattern-based transformation (empty list = no keywords)(when test body ...)is the pattern(if test (begin body ...) #f)is the template
Pattern Matching in Macros
Literals
Match specific symbols:
(define-syntax my-if
(syntax-rules (then else)
((my-if test then yes else no)
(if test yes no))))
(my-if (> 5 3) then "big" else "small")
; => "big"The symbols then and else must appear literally.
Ellipsis
The ... matches zero or more items:
> (define-syntax list-of
(syntax-rules ()
((list-of item ...)
(list item ...))))
> (list-of 1 2 3 4)
(1 2 3 4)
> (list-of)
()In the template, item ... expands to all matched items.
Multiple Patterns
Handle different cases:
(define-syntax my-or
(syntax-rules ()
((my-or) #f)
((my-or e) e)
((my-or e1 e2 ...)
(let ((t e1))
(if t t (my-or e2 ...))))))Patterns are tried in order until one matches.
Practical Macro Examples
unless
(define-syntax unless
(syntax-rules ()
((unless test body ...)
(if (not test)
(begin body ...)
#f))))and
(define-syntax and
(syntax-rules ()
((and) #t)
((and e) e)
((and e1 e2 ...)
(if e1 (and e2 ...) #f))))let* (sequential binding)
(define-syntax let*
(syntax-rules ()
((let* () body ...)
(begin body ...))
((let* ((var val) rest ...) body ...)
(let ((var val))
(let* (rest ...) body ...)))))case
(define-syntax case
(syntax-rules (else)
((case key)
#f)
((case key (else body ...))
(begin body ...))
((case key ((datum ...) body ...) rest ...)
(let ((tmp key))
(if (memv tmp '(datum ...))
(begin body ...)
(case tmp rest ...))))))Hygiene
Macros in Sigil are hygienic — they don't accidentally capture variables:
(define-syntax swap!
(syntax-rules ()
((swap! a b)
(let ((tmp a))
(set! a b)
(set! b tmp)))))
(define tmp 10)
(define x 1)
(define y 2)
(swap! x y)
;; tmp is still 10, not affected by macro's internal tmpThe tmp inside the macro doesn't clash with any tmp at the use site.
When to Use Macros
Use macros when you need to:
- Control evaluation: Delay or prevent evaluation of arguments
- Add new syntax: Create domain-specific constructs
- Avoid repetition: Generate repetitive code patterns
- Enforce patterns: Ensure certain code structures
Don't use macros when a procedure will do:
- Macros are harder to debug
- Macros don't compose as easily
- Procedure calls are more flexible
Debugging Macros
Use macroexpand to see what a macro expands to:
> (macroexpand '(when (> x 0) (display "positive")))
(if (> x 0) (begin (display "positive")) #f)This shows exactly what code your macro generates. Test with simple cases first:
> (macroexpand '(my-or))
#f
> (macroexpand '(my-or a))
a
> (macroexpand '(my-or a b c))
(let ((t a)) (if t t (my-or b c)))If the expansion looks wrong, adjust your patterns and templates.
Advanced Example: Loop Macro
A simple loop construct:
(define-syntax loop
(syntax-rules (for in do)
((loop for var in lst do body ...)
(for-each (lambda (var) body ...) lst))))
(loop for x in '(1 2 3)
do (println "~a" x))A counting loop:
(define-syntax repeat
(syntax-rules ()
((repeat n body ...)
(let loop ((i 0))
(when (< i n)
body ...
(loop (+ i 1)))))))
(repeat 3
(println "Hello!"))The Power of Quasiquote
When writing procedural macros (using lambda transformers), quasiquote is essential:
(define-syntax debug
(lambda (form)
(let ((expr (cadr form)))
`(begin
(display ',expr)
(display " = ")
(display ,expr)
(newline)))))
(define x 42)
(debug (+ x 1))
;; Prints: (+ x 1) = 43Best Practices
- Start simple: Begin with
syntax-rulesbefore procedural macros - Test incrementally: Test each pattern as you add it
- Document patterns: Explain what each pattern matches
- Prefer procedures: Only use macros when necessary
- Keep it readable: Complex macros should have clear structure
Practice Exercises
- Write a
whilemacro that loops while a condition is true. - Write a
dotimesmacro:(dotimes (i 5) (display i))prints 0-4. - Write an
assertmacro that prints the expression if it fails.
What's Next
Let's learn about error handling and debugging.