2
votes

I am a beginner in Racket as well as Lisp and was playing with syntax definitions. I defined a simple transformation like this:

(define-syntax hello
  (syntax-rules (in)
    ((_ name in world) (format "Hello ~a in ~a" name world))
    ((_ in name) (format "Hello ~a in here" name))))

Now, when I run it like these two cases, it works fine:

(hello "me" in "world") === "Hello me in world"

And,

(define in "inside")
(hello me in in)  === "Hello me in inside"

But, this results in an error,

(let ([in "Shire"])
  (hello "Martin" in in)) === Error: hello: bad syntax in: (hello "Martin" in in)

So, what is the reason for hello to fail here inside the let binding, whereas it works fine for a define? Also, where can I get more specific information regarding this difference? Thanks.

2
BTW, I just saw your question on #racket on IRC. You should definitely stick around for longer than 2 minutes. IRC is mostly a lurk-oriented medium; you get best results if you just post the actual question (without waiting for anyone to respond to a "can anybody help?" solicitation) and then wait 24 hours for a response. :-) (But, as you can tell, both lexi-lambda and I were able to answer here, so that worked out after all.)Chris Jester-Young

2 Answers

4
votes

This has to do with the way syntax literals work. In particular, a syntax literal is considered matched if:

  1. The literal does not have a binding at the point of macro definition, and also does not have a binding at the point of macro usage.
  2. The literal has a binding at the point of macro definition, and also has the same binding at the point of macro usage.

The define case worked for you because you probably put the define in the same module as the macro. Which means that criterion 2 matched: in has the same binding at both macro definition and usage. But, if you defined the macro in one module (without the define for in), and defined the in in a different module where you are using the macro, things won't work so well. :-)

The let case creates a new binding for in. That will never match the binding for in at the top-level macro definition.

4
votes

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)