I have some questions about how macros work in Scheme (specifically in Chicken Scheme), let's consider this example:
(define (when-a condition . body)
(eval `(if ,condition
(begin ,@body)
'())))
(define-syntax when-b
(er-macro-transformer
(lambda (exp rename compare)
(let ((condition (cadr exp))
(body (cddr exp)))
`(if ,condition (begin ,@body) '())))))
(define-syntax when-c
(ir-macro-transformer
(lambda (exp inject compare)
(let ((condition (cadr exp))
(body (cddr exp)))
`(if ,condition (begin ,@body) '())))))
(define-syntax when-d
(syntax-rules ()
((when condition body ...)
(if condition (begin body ...) '()))))
Can I consider when-a
a macro? I feel that I can't consider it a macro in a strict way since I'm not using define-syntax
but I'm not able to say any pratical reason to not prefer this implementation.
Are my macros hygienic?
Is there any difference between when-b
and when-c
? Since I'm not using rename
nor inject
I think there isn't.
Can I consider when-a a macro? I feel that I can't consider it a macro in a strict way since I'm not using define-syntax but I'm not able to say any pratical reason to not prefer this implementation.
This works like a macro, but it's not exactly the same as a true macro, for the following reasons:
(if #f (error "oops") '())
will evaluate to '()
but (when-a #f (error "oops"))
will raise an error.(eval '(define if "not a procedure"))
, for example, and that would mean this eval would fail; the if
in the body expression of the "expansion" doesn't refer to the if
at the definition site.Are my macros hygienic?
Only when-c
and when-d
are, because of the guarantees made by ir-macro-transformer
and syntax-rules
. In when-b
you'd have to rename if
and begin
to make them refer to the versions of if
and begin
at the macro definition site.
Example:
(let ((if #f))
(when-b #t (print "Yeah, ok")))
== expands to ==>
(let ((if1 #f))
(if1 #t (begin1 (print "Yeah, ok"))))
This will fail, because both versions of if
(here annotated with an extra 1
suffix) refer to the same thing, so we'll end up calling #f
in operator position.
In contrast,
(let ((if #f))
(when-c #t (print "Yeah, ok")))
== expands to ==>
(let ((if1 #f))
(if2 #t (begin1 (print "Yeah, ok"))))
Which will work as intended. If you want to rewrite when-b
to be hygienic, do it like this:
(define-syntax when-b
(er-macro-transformer
(lambda (exp rename compare)
(let ((condition (cadr exp))
(body (cddr exp))
(%if (rename 'if))
(%begin (rename 'begin)))
`(,%if ,condition (,%begin ,@body) '())))))
Note the extra %
-prefixed identifiers which refer to the original value of if
and begin
as they were at the place of definition of the macro.
Is there any difference between when-b and when-c? Since I'm not using rename nor inject I think there isn't.
There is. Implicit renaming macros are called that because they implicitly rename all the identifiers that come in from the usage site, and also every new identifier you introduce in the body. If you inject any identifiers, that undoes this implicit renaming, which makes them unhygienically available for capture by the calling code.
On the other hand, explicit renaming macros are called that because you must explicitly rename any identifiers to prevent them being captured by the calling code.