The reason for this is a little bit subtle—neither “should” work, but when the define
is at the top level, it actually alters the interpretation of syntax-rules
, which is where the behavior arises.
If you look at the documentation for syntax-rules
, you’ll see that each literal-id
is treated the same as in syntax-case
. The documentation notes that the literals are compared using free-identifier=?
:
An id
that has the same binding as a literal-id
matches a syntax object that is an identifier with the same binding in the sense of free-identifier=?
.
This means that literals are matches as bindings, not as datums. That is, if you have a local definition of in
in a let
, it is a separate binding from the in
marked as a literal within syntax-rules
, so they will not match. Furthermore, identifiers introduced with define
in a separate module will also not match.
Interestingly, if in
is exported from the module that defines the macro, then imported using rename-in
, the renamed binding will still work because Racket keeps track of renamings and considers them to be the same binding even if they are not the same name. These are orthogonal concepts, even if it’s sometimes easy to overlook all the nuances of that idea, but it’s somewhat intrinsic to hygiene.
Returning to the example in question, why does the following work?
(define-syntax hello
(syntax-rules (in)
((_ name in world) (format "Hello ~a in ~a" name world))
((_ in name) (format "Hello ~a in here" name))))
(define in "inside")
(hello "me" in in)
Well, definition contexts are mutually recursive, so the definition of in
after the macro definition is actually picked up by syntax-rules
. Therefore, the hello
macro actually expects the same binding as in
, so the call works. If, however, hello
is defined in a separate module from in
, the macro call will not work because the bindings would be different:
(module hello racket
(provide hello)
(define-syntax hello
(syntax-rules (in)
((_ name in world) (format "Hello ~a in ~a" name world))
((_ in name) (format "Hello ~a in here" name)))))
(require 'hello)
(define in "inside")
(hello "me" in in) ; => hello: bad syntax in: (hello "me" in in)