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.
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).