Why does foo have the same value as (foo) in this Racket macro?

Viewed 71

I am trying to understand macros inside the Racket environment. The concept intrigues me.

After writing this definition on Dr. Racket's definition window:

(define-syntax foo
    (lambda (stx)
      (syntax "I am foo")))

I used the REPL to call the following expressions:

> foo
"I am foo"

> (foo)
"I am foo"

These results surprise me. I was expecting something like a #procedure for the first call on foo.

Why (foo) and foo provide the same output?

Usually, I am pretty careful about adding parenthesis in Racket. Usually, they completely change the meaning of the expression being called. In this case, apparently, it makes no difference.

Thanks in advance.

1 Answers

Usually, I am pretty careful about adding parenthesis in Racket.

Yes, you are right to be careful. It usually makes a difference.

In your case, however, it doesn't seem to make a difference, because you are creating an overly simple macro that happens to expand in the same way whether the macro is invoked as a regular transformer or as an identifier macro.

I was expecting something like a #procedure for the first call on foo.

I want to address this first. Macros transform your program syntactically. For example, I can write a macro flip that flip the operands, so that

(flip foo 1 (let) bar baz 2)

is expanded (not evaluated) to:

(2 baz bar (let) 1 foo)

Again, I want to emphasize that this is a program transformation, like how you edit the code with your editor.

Now, let's write some actual macros:

(define-syntax bar
  (lambda (stx)
    (cond
      [(equal? (syntax->datum stx) '(bar abc def)) #'(+ 1 1)]
      [else #'(+ 2 2)])))

(bar abc def)      ;== expands => (+ 1 1) == evaluates => 2
(bar 42 (abc) qqq) ;== expands => (+ 2 2) == evaluates => 4
(bar)              ;== expands => (+ 2 2) == evaluates => 4

In the above macro, it checks if the input syntax is syntactically (bar abc def). If so, it transforms to (+ 1 1). Otherwise, it transforms to (+ 2 2).

All of these is to show you that it is unreasonable to expect a macro to result in "#procedure" (of course, unless the macro expands to a lambda), since what macro does is transforming syntax. It doesn't create a procedure.

The final mystery is what's going on with bare foo. Let's create a macro baz to understand that:

(define-syntax baz
  (lambda (stx)
    (cond
      [(equal? (syntax->datum stx) 'baz) #'1]
      [(equal? (syntax->datum stx) '(baz)) #'2]
      [else #'3])))

baz      ;== expands => 1
(baz)    ;== expands => 2
(baz 10) ;== expands => 3

It turns out that a bare identifier could also be a macro!

Now, consider your foo:

(define-syntax foo
    (lambda (stx)
      (syntax "I am foo")))

It's a transformation that ignores its operands, and always expands to "I am foo".

So:

(foo 1 2 3) ;== expands => "I am foo"
(foo x y z) ;== expands => "I am foo"
(foo)       ;== expands => "I am foo"
foo         ;== expands => "I am foo"

Note that in most macros, we use pattern matching to extract operands. Pattern matching can raise a syntax error when the input syntax doesn't match any pattern. This, for example, allows us to create a macro that doesn't allow it to be used as an identifier macro.

(define-syntax food
  (lambda (stx)
    (syntax-case stx ()
      ;; match when there is a parenthesis around the macro
      [(_ ...) #'1])))

(food) ;=> 1
food   ;=> food: bad syntax
Related