Reputation: 5494
I wish to expand
(foo x (f n) (f n) (arbitrary) (f n) ...)
into
(begin (x 'f n) (x 'f n) (arbitrary) (x 'f n) ...)
my attempt is:
(define-syntax foo
(syntax-rules ()
((_ l a ...)
(let-syntax ((f (syntax-rules ()
((_ n) (l (quote f) n)))))
(begin a ...)))))
(define (x t1 t2) (cons t1 t2)) ;; for example only
(define (arbitrary) (cons 'a 'b)) ;; for example only
(foo x (f 1) (f 2) (arbitrary) (f 3))
Using a macro stepper I can see that the first stage of the macro expands to
(let-syntax ((f (syntax-rules () ((_ n) (x 'f n)))))
(begin (f 1) (f 2) (arbitrary) (f 3)))
Which, when evaluated in isolation works perfectly, but when executed as a whole I get an error about f
being an undefined identifier. I assume this is an issue in scoping, is this type of macro expansion possible?
Upvotes: 1
Views: 1411
Reputation: 29546
Yeah, you need to get f
from somewhere -- your macro just makes it up, and therefore it is not visible to users of foo
. When you do consider that you need to get it from somewhere, the question is where would you get it from? Here's a fixed version of your code that assumes that it is the first thing in the second subform of foo
:
(define-syntax foo
(syntax-rules ()
[(_ l (f a) more ...)
(let-syntax ([f (syntax-rules ()
[(_ n) (l 'f n)])])
(list (f a) more ...))]))
(define (x t1 t2) (cons t1 t2))
(define (arbitrary) (cons 'a 'b))
(foo x (f 1) (f 2) (arbitrary) (f 3))
(I also made it expand into a list
to see that all forms are transformed.)
However, if you want a global kind of f
to be used inside foo
, then you really have to do just that: define a global f
. Here's a limited way to do that:
;; no body => using `f' is always an error
(define-syntax f (syntax-rules ()))
(define-syntax foo
(syntax-rules ()
[(_ l a ...) (list (foo-helper l a) ...)]))
(define-syntax foo-helper
(syntax-rules (f) ; match on f and transform it
[(_ l (f n)) (l 'f n)]
[(_ l a) a]))
(define (x t1 t2) (cons t1 t2))
(define (arbitrary) (cons 'a 'b))
(foo x (f 1) (f 2) (arbitrary) (f 3))
The main limitation in this is that it will only work if one of the a
forms is using f
-- but it won't work if it is nested in an expression. For example, this will throw a syntax error:
(foo x (f 1) (f 2) (arbitrary)
(let ([n 3]) (f n)))
You can imagine complicating foo-helper
and make it scan its input recursively, but that's a slippery slope you don't want to get into. (You'll need to make special cases for places like inside a quote
, in a binding, etc.)
The way to solve that in Racket (and recently in Guile too) is to use a syntax parameter. Think about this as binding f
to the same useless macro using define-syntax-parameter
, and then use syntax-parameterize
to "adjust" its meaning inside a foo
to a macro that does the transformation that you want. Here's how this looks like:
;; needed to get syntax parameters
(require racket/stxparam)
;; same useless definition, but as a syntax parameter
(define-syntax-parameter f (syntax-rules ()))
(define-syntax foo
(syntax-rules ()
[(_ l a ...)
;; adjust it inside these forms
(syntax-parameterize ([f (syntax-rules ()
[(_ n) (l 'f n)])])
(list a ...))]))
(define (x t1 t2) (cons t1 t2))
(define (arbitrary) (cons 'a 'b))
(foo x (f 1) (f 2) (arbitrary)
(let ([n 3]) (f n)))
Upvotes: 3