Search code examples
macrosracketread-eval-print-loop

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


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.


Solution

  • 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