Some basic questions about macros and scope

Recently, I have been reading Fear of Macros to understand some basic usage of macros in Racket. I found everything relatively easy to understand until I encountered the content related to lexical context.

If my understanding is correct, a syntax object contains at least a piece of literal program and the binding information of the symbols within it. For example, if I write

(define x 1)
(define s #'(list x y))
(define y 2)

In DrRacket, there will be an arrow from the first x to the x in the syntax object, and the same for y. That is to say, #'... captures the binding environment at its location and then embeds it into the syntax object it represents.

However, I am confused about the use of #' within a macro (i.e., define-syntax block). In Fear of Macros, the author first performs syntax transformation through datum->syntax, explicitly providing the lexical context stx:

; Fear of Macros, beginning of Chapter 4
(require (for-syntax racket/match))
(define-syntax (our-if-using-match-v2 stx)
  (match (syntax->list stx)
    [(list _ condition true-expr false-expr)
     (datum->syntax stx `(cond [,condition ,true-expr]
                               [else ,false-expr]))]))

After introducing syntax-case, the above code segment is written as

; Below the above code segment
(define-syntax (our-if-using-syntax-case stx)
    (syntax-case stx ()
      [(_ condition true-expr false-expr)
       #'(cond [condition true-expr]
               [else false-expr])]))

What environment does #' capture here? Is it stx, or something else?

Another point that confuses me is about a description of format-id (4.1.3). For the program

; Fear of Macros: 4.1.3
> (require (for-syntax racket/syntax))
> (define-syntax (hyphen-define/ok3 stx)
    (syntax-case stx ()
      [(_ a b (args ...) body0 body ...)
       (with-syntax ([name (format-id #'a "~a-~a" #'a #'b)])
         #'(define (name args ...)
             body0 body ...))]))
> (hyphen-define/ok3 bar baz () #t)
> (bar-baz)

The author mentions

The first argument of format-id, lctx, is the lexical context of the identifier that will be created. You almost never want to supply stx—the overall chunk of syntax that the macro transforms. Instead, you want to supply some more specific bit of syntax, such as an identifier that the user has provided to the macro. In this example, we’re using #'a. The resulting identifier will have the same scope as that which the user provided. This is more likely to behave as the user expects, especially when our macro is composed with other macros.

I don't quite understand this part. For example, in the code segment above, i.e., (hyphen-define/ok3 bar baz () #t), the binding environment of bar and (hyphen-define/ok3 bar baz () #t) seems to be the same. Is my understanding incorrect, or is it indeed possible to construct a situation where the binding environments of the two are different?


These are my two main questions. As a Racket beginner, I hope to understand some language concepts as accurately as possible to avoid greater misunderstandings. So, if you notice any potential deviations in my understanding of any concepts from my description, I hope you can point them out directly.

Thank you very much for reading and in advance for your answers!

(Sorry for my poor English, this question is mostly translated by GPT.)


Append

I'm trying another program:

(define x 1)
(define s #'(list x y))
(define y 2)
;;;
(define s2
  (let ([z 3])
    #'z))

This time, the z in the syntax object isn't bound. I'm confused with this. What's the difference between z and x, y? I know that if I change let to with-syntax, z will be bound, but why?


Append 2

I further read The Racket Reference - Syntax Model, and I think my previous understanding of #' or (syntax ...) was incorrect. In fact, (syntax ...) does not capture any non-global environment outside. The x and y in s are bound merely because x and y are top-level variables. Is this correct?

1 Like

If my understanding is correct, a syntax object contains at least a piece of literal program and the binding information of the symbols within it.

It is worth having the following in mind:

A syntax object represents a program (or a piece of a program).
Macro expansion is a process that begins with a syntax object with no (or limited) binding information. During macro expansion the bindings structure is discovered and bindings information is added to the output of the macro expansion (also a syntax object).
The information available during the expansion might only be partial.

I can recommend watching the first 10-15 minutes of Flatt's talk "Let's Build a Hygienic Macro Expander". The introduction is all about scopes and how they are represented.

FWIW the documentation for #' aka quote-syntax is here:

https://docs.racket-lang.org/reference/Syntax_Quoting__quote-syntax.html#(form._((quote._~23~25kernel)._quote-syntax))

1 Like

The syntax form, or rather, quote-syntax form (without #:local) performs what is called “scope pruning”, “capturing” up to a certain point, otherwise the use of binding forms in macros will be impossible.

1 Like

@soegaard @usao

Thank you! I've learned a lot from your answers.