简体   繁体   中英

How do I rename a procedure with macros in Racket?

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:

1.

(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:

2.

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

http://www.scheme.com/tspl4/syntax.html#./syntax:s61

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM