Macro-generated defines aren't recognized by module-macroexpand
When generating a define with a macro, such as this:
(define-syntax test
(syntax-rules ()
((_ a)
(define asdf (lambda () 'aaa)))))
(test 2)
(asdf)
You get this error: These variables are undefined: (asdf) #<module-reference #3 loader: #<loader #4 name: local> path...
However, when you execute the same code in Blackhole REPL, it does work. Commenting out the checks done at module-macroexpand (around line 482: undefined-names) makes it work, but of course you loose the undefined variables check, which is a nice thing to have.
Interesting.
Actually, as I understand it, it is the REPL's behavior that is incorrect. syntax-rules macros are hygienic, with no option for opting out of the hygiene. That also implies that you can't export a name from a syntax-rules macro unless you actually put the name as a parameter to the macro. For instance,
(define-syntax test
(syntax-rules ()
((_ a)
(define a (lambda () 'aaa)))))
(test asdf)
(asdf)
ought to work as intended. (I haven't tested this though.)
The reason it works in the REPL is because
- To allow for mutually recursive top level functions, it allows usage of undefined names, assuming that they might be defined in the future
- The original syntax-rules macro that you presented does generate a top level binding, even though it ought to be hidden.
The second point above is where the error is: since it is a top level REPL binding it simply calls it ~#asdf. This is wrong, since you should be able to create your own top level asdf REPL binding, and it should even be possible to invoke the macro several times and have it generate unique top level bindings.
Simply expanding the define into something equivalent to a let would be correct in this particular case, but not in general:
(define-syntax test2
(syntax-rules ()
((test2 name value)
(define tmp value)
(define (name) (tmp)))))
(test2 one 1)
(test2 two 2)
(pp (+ (one) (two)))
The previous code snippet should print 3, and in the top level there should be 5 bindings:
- The
test2macro - The
tmpname generated by the first invocation oftest2, with the value 1 - The
tmpname generated by the second invocation oftest2, with the value 2 - The
onefunction, which accesses the firsttmpname - The
twofunction, which accesses the secondtmpname
Black Hole sure doesn't do the right thing in this case; it lacks the logic of creating several unique top level names with the same name. It would (I think) do the right thing if you wrapped the whole thing in a let though.