sigildocs

Macros

Syntax transformers with define-syntax, syntax-rules, and syntax-case.

Overview

Macros transform code at compile time. They receive syntax (code as data) and return new syntax to be compiled.

define-syntax

Define a macro transformer.

(define-syntax macro-name transformer)

The transformer is typically created with syntax-rules or syntax-case.

syntax-rules

Pattern-based macros. Each clause matches a pattern and produces output. Macros defined with syntax-rules are hygienic—introduced identifiers don't accidentally capture user bindings.

(define-syntax when
  (syntax-rules ()
    ((when test body ...)
     (if test (begin body ...) #f))))

(when (> x 0)
  (display "positive")
  (newline))
; Expands to:
; (if (> x 0) (begin (display "positive") (newline)) #f)

Pattern Syntax

  • _ - Matches anything, not bound
  • name - Matches anything, bound to name
  • (pattern ...) - Matches zero or more
  • (pattern . rest) - Matches list with rest
  • literal - Matches exact symbol (if in literals list)

Literals

Symbols in the literals list match exactly, not as pattern variables.

(define-syntax my-cond
  (syntax-rules (else =>)
    ((my-cond (else result ...))
     (begin result ...))
    ((my-cond (test => proc))
     (let ((temp test))
       (if temp (proc temp) #f)))
    ((my-cond (test result ...))
     (if test (begin result ...) #f))
    ((my-cond (test result ...) clause ...)
     (if test
         (begin result ...)
         (my-cond clause ...)))))

Multiple Clauses

(define-syntax my-or
  (syntax-rules ()
    ((my-or) #f)
    ((my-or e) e)
    ((my-or e1 e2 ...)
     (let ((temp e1))
       (if temp temp (my-or e2 ...))))))

syntax-case

Procedural macros with full Scheme power. More flexible than syntax-rules. Macros defined with syntax-case are also hygienic.

(define-syntax my-let
  (lambda (stx)
    (syntax-case stx ()
      ((_ ((var val) ...) body ...)
       #'((lambda (var ...) body ...) val ...)))))

Syntax Objects

  • #'template - Create syntax from template (like quasiquote for syntax)
  • #template - Quasisyntax, allows #,` for unquote
  • syntax->datum - Convert syntax to plain data
  • datum->syntax - Convert data to syntax (with lexical context)

Guards

Add conditions to pattern clauses.

(define-syntax assert-symbol
  (lambda (stx)
    (syntax-case stx ()
      ((_ x)
       (identifier? #'x)
       #'(quote x))
      ((_ x)
       (syntax-error "expected identifier" #'x)))))

Building Syntax Programmatically

(define-syntax define-predicates
  (lambda (stx)
    (syntax-case stx ()
      ((_ name ...)
       (with-syntax (((pred ...)
                      (map (lambda (n)
                             (datum->syntax n
                               (string->symbol
                                 (string-append
                                   (symbol->string (syntax->datum n))
                                   "?"))))
                           #'(name ...))))
         #'(begin
             (define (pred x) (eq? x 'name))
             ...))))))

(define-predicates red green blue)
; Defines red?, green?, blue?

Common Patterns

Binding Forms

(define-syntax let1
  (syntax-rules ()
    ((let1 var val body ...)
     (let ((var val)) body ...))))

Anaphoric Macros

To intentionally inject identifiers into user scope, use datum->syntax with the user's syntax object:

(define-syntax aif
  (lambda (stx)
    (syntax-case stx ()
      ((_ test then else)
       (with-syntax ((it (datum->syntax stx 'it)))
         #'(let ((it test))
             (if it then else)))))))

(aif (find-user id)
     (display (user-name it))
     (display "not found"))

Loop Constructs

(define-syntax while
  (syntax-rules ()
    ((while test body ...)
     (let loop ()
       (when test
         body ...
         (loop))))))

Delayed Evaluation

(define-syntax delay
  (syntax-rules ()
    ((delay expr)
     (lambda () expr))))

(define-syntax force
  (syntax-rules ()
    ((force promise)
     (promise))))

Hygiene

Macros defined with syntax-rules and syntax-case are hygienic by default. Introduced bindings don't capture user names.

(define-syntax swap!
  (syntax-rules ()
    ((swap! a b)
     (let ((temp a))
       (set! a b)
       (set! b temp)))))

(let ((temp 1)
      (x 2))
  (swap! temp x)
  (list temp x))
; => (2 1)
; The macro's temp doesn't interfere with user's temp

Plain transformer procedures (raw lambdas without syntax-case) are not automatically hygienic.