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
ffrom 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
listto see that all forms are transformed.)However, if you want a global kind of
fto 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
aforms 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-helperand 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
fto the same useless macro usingdefine-syntax-parameter, and then usesyntax-parameterizeto "adjust" its meaning inside afooto a macro that does the transformation that you want. Here's how this looks like: