Let's say I want to replace all occurrences of an procedure at compile time, for example all occurrences of cons
with 😄
. I tried two options that seemed natural:
(define-syntax 😄
(syntax-rules ()
[(_ x y) (cons x y)]))
This works if I do something like (😄 2 3)
, but I can't use 😄
if it's not in application position, so I get a 'bad syntax' error if I do (map 😄 '(1 2) '(3 4))
.
Then I thought to just replace the identifier itself:
(define-syntax 😄
(λ (_) #'cons))
Now 😄
by itself gives me #<procedure:cons>
and (map 😄 '(1 2) '(3 4))
gives the correct answer, but with (😄 2 3)
the syntax transformer is taking all the arguments and replaces the whole expression with cons
, which is not what I wanted.
How do I achieve what I want? Is there some kind of transformer that does this?
UPDATE: As I typed the last sentence, I invoked the "Rubber duck effect" and found make-rename-transformer
which is the thing I want. However, the docs say "Such a transformer could be written manually", and it seems I have failed to do so with my 2 tries. How do you manually make such a transformer? (ignoring the bullet points in the docs for make-syntax-transformer
)
Also, I know I can just use (define 😄 cons)
but then that's a whole extra function call at runtime.
The absolute easiest (and probably best) way to do this would be to use rename-in
to import an identifier under a different name:
(require (rename-in racket/base [cons 😄]))
This is also the most direct way to do it, since it won't create a syntax transformer at all—it will create a new binding that is identical to cons
in every way, including free-identifier=?
. From the compiler's perspective, that makes cons
and 😄
indistinguishable.
However, this is a little bit magical. Another approach would be to manually create a rename transformer using make-rename-transformer
:
(define-syntax 😄 (make-rename-transformer #'cons))
If you can't use rename-in
, using a rename transformer is the next best thing, since the free-identifier=?
function recognizes rename transformers specially, so (free-identifier=? #'cons #'😄)
will still be #t
. This is useful for macros that care about syntax bindings, since 😄
will be accepted in all the same places cons
would be.
Still, this is using some sort of primitive. If you really wanted to, how could you implement make-rename-transformer
yourself? Well, the key here is that macro application can appear in two forms in Racket. If you have a macro foo
, then it can be used in either of these ways:
foo
(foo ...)
The first case is an “id macro”, when a macro is used as a bare identifier, and the second is the more common case. If you want to write a macro that works just like an expression, though, you need to worry about both cases. You cannot do this with syntax-rules
, which doesn't allow id macros, but you can do this with syntax-id-rules
:
(define-syntax 😄
(syntax-id-rules ()
[(_ . args) (cons . args)]
[_ cons]))
This pattern will make 😄
expand as expected in both scenarios. However, unlike the above two approaches, it will not cooperate with free-identifier=?
, since 😄
is now a whole new binding bound to an ordinary macro from the macroexpander's perspective.
From that point of view, is make-rename-transformer
magical? Not really, but it is special in the sense that free-identifier=?
handles it as a special case. If you wanted to, you could implement your own make-rename-transformer
function and your own free-identifier=?
function to get similar behavior:
(begin-for-syntax
(struct my-rename-transformer (id-stx)
#:property prop:procedure
(λ (self stx)
(with-syntax ([id (my-rename-transformer-id-stx self)])
((set!-transformer-procedure
(syntax-id-rules ()
[(_ . args) (id . args)]
[_ id]))
stx))))
(define (my-rename-target id-stx)
(let ([val (syntax-local-value id-stx (λ () #f))])
(if (my-rename-transformer? val)
(my-rename-target (my-rename-transformer-id-stx val))
id-stx)))
(define (my-free-identifier=? id-a id-b)
(free-identifier=? (my-rename-target id-a)
(my-rename-target id-b))))
This will allow you to do this:
(define-syntax 😄 (my-rename-transformer #'cons))
…and (my-free-identifier=? #'😄 #'cons)
will be #t
. However, it's not recommended that you actually do this, for obvious reasons: not only is it unnecessary, it won't really work, given that other macros won't use my-free-identifier=?
, just the ordinary free-identifier=?
. For that reason, it's highly recommended that you just use make-rename-transformer
instead.
If you manually want to write such a macro, you need to use make-set!-transformer
.
http://docs.racket-lang.org/reference/stxtrans.html?q=set-transformer#%28def. %28%28quote. ~23~25kernel%29._make-set%21-transformer%29%29
Note that you need to handle both assignments (set! xe)
and references x
and applications (x arg0 arg1 ...)
needs to be handled by the clauses in your syntax-case
expression.
UPDATE
In Dybvig's book "The Scheme Programming Language" he has an example of a macro define-integrable
that sounds to be just the thing you are after.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.