6.8.7 Syntax Parameters

Syntax parameters16 are a mechanism for rebinding a macro definition within the dynamic extent of a macro expansion. This provides a convenient solution to one of the most common types of unhygienic macro: those that introduce a unhygienic binding each time the macro is used. Examples include a lambda form with a return keyword, or class macros that introduce a special self binding.

With syntax parameters, instead of introducing the binding unhygienically each time, we instead create one binding for the keyword, which we can then adjust later when we want the keyword to have a different meaning. As no new bindings are introduced, hygiene is preserved. This is similar to the dynamic binding mechanisms we have at run-time (see parameters), except that the dynamic binding only occurs during macro expansion. The code after macro expansion remains lexically scoped.

Syntax: define-syntax-parameter keyword transformer

Binds keyword to the value obtained by evaluating transformer. The transformer provides the default expansion for the syntax parameter, and in the absence of syntax-parameterize, is functionally equivalent to define-syntax. Usually, you will just want to have the transformer throw a syntax error indicating that the keyword is supposed to be used in conjunction with another macro, for example:

(define-syntax-parameter return
  (lambda (stx)
    (syntax-violation 'return "return used outside of a lambda^" stx)))
Syntax: syntax-parameterize ((keyword transformer) …) exp …

Adjusts keyword … to use the values obtained by evaluating their transformer …, in the expansion of the exp … forms. Each keyword must be bound to a syntax-parameter. syntax-parameterize differs from let-syntax, in that the binding is not shadowed, but adjusted, and so uses of the keyword in the expansion of exp … use the new transformers. This is somewhat similar to how parameterize adjusts the values of regular parameters, rather than creating new bindings.

(define-syntax lambda^
  (syntax-rules ()
    [(lambda^ argument-list body body* ...)
     (lambda argument-list
       (call-with-current-continuation
        (lambda (escape)
          ;; In the body we adjust the 'return' keyword so that calls
          ;; to 'return' are replaced with calls to the escape
          ;; continuation.
          (syntax-parameterize ([return (syntax-rules ()
                                          [(return vals (... ...))
                                           (escape vals (... ...))])])
            body body* ...))))]))

;; Now we can write functions that return early.  Here, 'product' will
;; return immediately if it sees any 0 element.
(define product
  (lambda^ (list)
           (fold (lambda (n o)
                   (if (zero? n)
                       (return 0)
                       (* n o)))
                 1
                 list)))

Footnotes

(16)

Described in the paper Keeping it Clean with Syntax Parameters by Barzilay, Culpepper and Flatt.