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.
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