16.2.5 Compile and Run-Time Phases

► |  | The Racket Guide

1 | Welcome to Racket
2 | Racket Essentials
3 | Built-In Datatypes
4 | Expressions and Definitions
5 | Programmer-Defined Datatypes
6 | Modules
7 | Contracts
8 | Input and Output
9 | Regular Expressions
10 | Exceptions and Control
11 | Iterations and Comprehensions
12 | Pattern Matching
13 | Classes and Objects
14 | Units
15 | Reflection and Dynamic Evaluation
16 | Macros
17 | Creating Languages
18 | Concurrency and Synchronization
19 | Performance
20 | Parallelism
21 | Running and Creating Executables
22 | More Libraries
23 | Dialects of Racket and Scheme
24 | Command-Line Tools and Your Editor of Choice
| Bibliography
| Index

► | 16 | Macros

16.1 | Pattern-Based Macros
16.2 | General Macro Transformers
16.3 | Module Instantiations and Visits

▼ | 16.2 | General Macro Transformers

16.2.1 | Syntax Objects
16.2.2 | Macro Transformer Procedures
16.2.3 | Mixing Patterns and Expressions: syntax-case
16.2.4 | with-syntax and generate-temporaries
16.2.5 | Compile and Run-Time Phases
16.2.6 | General Phase Levels
16.2.7 | Tainted Syntax

top contents ← prev up next →

##### 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 messages, 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 an identifier before its definition
> in module: top-level

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

top contents ← prev up next →