macrosschemeracketdefine-syntax

Capturing Macros in Scheme


What's the simplest way to define a capturing macro using define-syntax or define-syntax-rule in Racket?

As a concrete example, here's the trivial aif in a CL-style macro system.

(defmacro aif (test if-true &optional if-false)
    `(let ((it ,test))
        (if it ,if-true ,if-false)))

The idea is that it will be bound to the result of test in the if-true and if-false clauses. The naive transliteration (minus optional alternative) is

(define-syntax-rule (aif test if-true if-false)
    (let ((it test))
       (if it if-true if-false)))

which evaluates without complaint, but errors if you try to use it in the clauses:

> (aif "Something" (displayln it) (displayln "Nope")))
reference to undefined identifier: it

The anaphora egg implements aif as

(define-syntax aif
  (ir-macro-transformer
   (lambda (form inject compare?)
     (let ((it (inject 'it)))
       (let ((test (cadr form))
         (consequent (caddr form))
         (alternative (cdddr form)))
     (if (null? alternative)
         `(let ((,it ,test))
        (if ,it ,consequent))
         `(let ((,it ,test))
        (if ,it ,consequent ,(car alternative)))))))))

but Racket doesn't seem to have ir-macro-transformer defined or documented.


Solution

  • Racket macros are designed to avoid capture by default. When you use define-syntax-rule it will respect lexical scope.

    When you want to "break hygiene" intentionally, traditionally in Scheme you have to use syntax-case and (carefully) use datum->syntax.

    But in Racket the easiest and safest way to do "anaphoric" macros is with a syntax parameter and the simple define-syntax-rule.

    For example:

    (require racket/stxparam)
    
    (define-syntax-parameter it
      (lambda (stx)
        (raise-syntax-error (syntax-e stx) "can only be used inside aif")))
    
    (define-syntax-rule (aif condition true-expr false-expr)
      (let ([tmp condition])
        (if tmp
            (syntax-parameterize ([it (make-rename-transformer #'tmp)])
              true-expr)
            false-expr)))
    

    I wrote about syntax parameters here and also you should read Eli Barzilay's Dirty Looking Hygiene blog post and Keeping it Clean with Syntax Parameters paper (PDF).