16.2.5 Compile and Run-Time Phases

As sets of macros get more complicated, you might want to write your own helper functions, like generate-temporaries. For example, to provide good syntax error messsages, swap, rotate, and define-cbr all should check that certain sub-forms in the source form are identifiers. We could use a check-ids function to perform this checking everywhere:

(define-syntax (swap stx)
  (syntax-case stx ()
    [(swap x y) (begin
                  (check-ids stx #'(x y))
                  #'(let ([tmp x])
                      (set! x y)
                      (set! y tmp)))]))
 
(define-syntax (rotate stx)
  (syntax-case stx ()
    [(rotate a c ...)
     (begin
       (check-ids stx #'(a c ...))
       #'(shift-to (c ... a) (a c ...)))]))

The check-ids function can use the syntax->list function to convert a syntax-object wrapping a list into a list of syntax objects:

(define (check-ids stx forms)
  (for-each
   (lambda (form)
     (unless (identifier? form)
       (raise-syntax-error #f
                           "not an identifier"
                           stx
                           form)))
   (syntax->list forms)))

If you define swap and check-ids in this way, however, it doesn’t work:

> (let ([a 1] [b 2]) (swap a b))

check-ids: undefined;

 cannot reference undefined identifier

The problem is that check-ids is defined as a run-time expression, but swap is trying to use it at compile time. In interactive mode, compile time and run time are interleaved, but they are not interleaved within the body of a module, and they are not interleaved across modules that are compiled ahead-of-time. To help make all of these modes treat code consistently, Racket separates the binding spaces for different phases.

To define a check-ids function that can be referenced at compile time, use begin-for-syntax:

(begin-for-syntax
  (define (check-ids stx forms)
    (for-each
     (lambda (form)
       (unless (identifier? form)
         (raise-syntax-error #f
                             "not an identifier"
                             stx
                             form)))
     (syntax->list forms))))

With this for-syntax definition, then swap works:

> (let ([a 1] [b 2]) (swap a b) (list a b))

'(2 1)

> (swap a 1)

eval:13:0: swap: not an identifier

  at: 1

  in: (swap a 1)

When organizing a program into modules, you may want to put helper functions in one module to be used by macros that reside on other modules. In that case, you can write the helper function using define:

"utils.rkt"

#lang racket
 
(provide check-ids)
 
(define (check-ids stx forms)
  (for-each
   (lambda (form)
     (unless (identifier? form)
       (raise-syntax-error #f
                           "not an identifier"
                           stx
                           form)))
   (syntax->list forms)))

Then, in the module that implements macros, import the helper function using (require (for-syntax "utils.rkt")) instead of (require "utils.rkt"):

#lang racket
 
(require (for-syntax "utils.rkt"))
 
(define-syntax (swap stx)
  (syntax-case stx ()
    [(swap x y) (begin
                  (check-ids stx #'(x y))
                  #'(let ([tmp x])
                      (set! x y)
                      (set! y tmp)))]))

Since modules are separately compiled and cannot have circular dependencies, the "utils.rkt" module’s run-time body can be compiled before the compiling the module that implements swap. Thus, the run-time definitions in "utils.rkt" can be used to implement swap, as long as they are explicitly shifted into compile time by (require (for-syntax ....)).

The racket module provides syntax-case, generate-temporaries, lambda, if, and more for use in both the run-time and compile-time phases. That is why we can use syntax-case in the racket REPL both directly and in the right-hand side of a define-syntax form.

The racket/base module, in contrast, exports those bindings only in the run-time phase. If you change the module above that defines swap so that it uses the racket/base language instead of racket, then it no longer works. Adding (require (for-syntax racket/base)) imports syntax-case and more into the compile-time phase, so that the module works again.

Suppose that define-syntax is used to define a local macro in the right-hand side of a define-syntax form. In that case, the right-hand side of the inner define-syntax is in the meta-compile phase level, also known as phase level 2. To import syntax-case into that phase level, you would have to use (require (for-syntax (for-syntax racket/base))) or, equivalently, (require (for-meta 2 racket/base)). For example,

#lang racket/base
(require  ;; This provides the bindings for the definition
          ;; of shell-game.
          (for-syntax racket/base)
 
          ;; And this for the definition of
          ;; swap.
          (for-syntax (for-syntax racket/base)))
 
(define-syntax (shell-game stx)
 
  (define-syntax (swap stx)
    (syntax-case stx ()
      [(_ a b)
       #'(let ([tmp a])
           (set! a b)
           (set! b tmp))]))
 
  (syntax-case stx ()
    [(_ a b c)
     (let ([a #'a] [b #'b] [c #'c])
       (when (= 0 (random 2)) (swap a b))
       (when (= 0 (random 2)) (swap b c))
       (when (= 0 (random 2)) (swap a c))
       #`(list #,a #,b #,c))]))
 
(shell-game 3 4 5)
(shell-game 3 4 5)
(shell-game 3 4 5)

Negative phase levels also exist. If a macro uses a helper function that is imported for-syntax, and if the helper function returns syntax-object constants generated by syntax, then identifiers in the syntax will need bindings at phase level -1, also known as the template phase level, to have any binding at the run-time phase level relative to the module that defines the macro.

For instance, the swap-stx helper function in the example below is not a syntax transformer—it’s just an ordinary function—but it produces syntax objects that get spliced into the result of shell-game. Therefore, its containing helper submodule needs to be imported at shell-game’s phase 1 with (require (for-syntax 'helper)).

But from the perspective of swap-stx, its results will ultimately be evaluated at phase level -1, when the syntax returned by shell-game is evaluated. In other words, a negative phase level is a positive phase level from the opposite direction: shell-game’s phase 1 is swap-stx’s phase 0, so shell-game’s phase 0 is swap-stx’s phase -1. And that’s why this example won’t work—the 'helper submodule has no bindings at phase -1.

#lang racket/base
(require (for-syntax racket/base))
 
(module helper racket/base
  (provide swap-stx)
  (define (swap-stx a-stx b-stx)
    #`(let ([tmp #,a-stx])
          (set! #,a-stx #,b-stx)
          (set! #,b-stx tmp))))
 
(require (for-syntax 'helper))
 
(define-syntax (shell-game stx)
  (syntax-case stx ()
    [(_ a b c)
     #`(begin
         #,(swap-stx #'a #'b)
         #,(swap-stx #'b #'c)
         #,(swap-stx #'a #'c)
         (list a b c))]))
 
(define x 3)
(define y 4)
(define z 5)
(shell-game x y z)

To repair this example, we add (require (for-template racket/base)) to the 'helper submodule.

#lang racket/base
(require (for-syntax racket/base))
 
(module helper racket/base
  (require (for-template racket/base)) ; binds `let` and `set!` at phase -1
  (provide swap-stx)
  (define (swap-stx a-stx b-stx)
    #`(let ([tmp #,a-stx])
          (set! #,a-stx #,b-stx)
          (set! #,b-stx tmp))))
 
(require (for-syntax 'helper))
 
(define-syntax (shell-game stx)
  (syntax-case stx ()
    [(_ a b c)
     #`(begin
         #,(swap-stx #'a #'b)
         #,(swap-stx #'b #'c)
         #,(swap-stx #'a #'c)
         (list a b c))]))
 
(define x 3)
(define y 4)
(define z 5)
(shell-game x y z)
(shell-game x y z)
(shell-game x y z)