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.