Macros expanding to definitions
This post is about macros that expand into definitions.
The examples will use syntax/parse, so all snippets
work if they are preceded with:
#lang racket
(require (for-syntax racket/syntax syntax/parse))
Scope sets and bindings
The first example is the macro define-a.
1 (define-syntax (define-a stx)
2 (syntax-parse stx
3 [(_define-a expr)
4 #'(define a expr)]))
5
6 (define-a 42)
7 a ; unbound identifier
The variable reference a on line 7 results in an unbound identifier error.
Why?
During macro expansion the expander attaches sets of scopes to each identifier.
When the macro application (define-a 42) is expanded a new scope s,
is created and attached to the variables bound in the syntax fragment
produced by the macro. The expansion becomes:
(define a_{s} 42)
The variable reference a on line 7, has no extra scopes attached,
so the reference is a_{}.
The scope sets are used to determine whether one variable can bind another.
A reference’s binding is found as one whose set of scopes is the
largest subset of the reference’s own scopes (in addition to having
the same symbolic name).
We now see, why a_{s} doesn't bind a_{}. The problem is that {s}
is not a subset of {}.
Copying the lexical context
If we want the definition to bind a_{} we must produce an identifier a,
that has no extra scopes. We can use format-id (or datum->syntax)
for this. A little simplified, a call to format-id looks like this:
(format-id lexical-context format-string fmt value ...)
The syntax transformer for define-a receives the syntax object
to expand (define-a 42) as the argument stx. Using stx as
the lexical context for a copies the scope set attached to stx
to our new identifier. Effectively giving us a an identifer a
with the same lexical context as (define-a 42). And that
context is the same as the reference a on line 9.
1 (define-syntax (define-a stx)
2 (syntax-parse stx
3 [(_define-a expr)
4 (define a (format-id stx "~a" 'a))
5 (with-syntax ([a a])
6 #'(define a expr))]))
7
8 (define-a 42)
9 a ;; evaluates to 42
Now our define-a introduces an a defined in the context where the macro was called.
Expansion to a macro, that expands into a definition
Let's attempt to define a macro that uses define-a in the expansion.
1 (define-syntax (define-a stx)
2 (syntax-parse stx
3 [(_define-a expr)
4 (define a (format-id stx "~a" 'a))
5 (with-syntax ([a a])
6 #'(define a expr))]))
7
8 (define-syntax (define-a42 stx)
9 (syntax-parse stx
10 [(_define-a42)
11 #'(define-a 42)]))
12
13 (define-a42)
14 a ;; unbound variable
The intention was that define-a42 where to define a variable a with
an initial value 42. However, we get an unbound identifier error again.
Can you see why?
Let's attempt to track the expansion of (define-a42).
The transformer returns #'(define a 42) but each macro call
introduces a new scope, so if we call the new scope t, it returns:
(define-a_{t} 42)
The expander then calls the transformer of define-a with stx
bound to (define-a_{t} 42). That macro call introduces a new scope s.
The return value will be [we are ignoring the scope set for define]:
(define a_{t} 42)
Since the scope set {t} is not a subset of {} a_{t} does not bind a_{}.
Fixing the example
Since define-a uses the context of stx for the variable a,
we can fix the example above like this:
(define-syntax (define-a42 stx)
(syntax-parse stx
[(_define-a42)
(syntax-local-introduce
#'(define-a 42))]))
The syntax-local-introduce removes the scope normally
added by each macro call.