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?
Yeah, you need to get
f
from somewhere -- your macro just makes it up, and therefore it is not visible to users offoo
. 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 offoo
:(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 insidefoo
, then you really have to do just that: define a globalf
. Here's a limited way to do that:The main limitation in this is that it will only work if one of the
a
forms is usingf
-- but it won't work if it is nested in an expression. For example, this will throw a syntax error: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 aquote
, 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 usingdefine-syntax-parameter
, and then usesyntax-parameterize
to "adjust" its meaning inside afoo
to a macro that does the transformation that you want. Here's how this looks like: