2
votes

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.

2
With (define new-var old-var) you do not introduce any operation at run-time. You are just defining that the same value has two different names, and you can use both of them without incurring in any inefficiency (you are just making your code more difficult to read). So if old-var is a function, by calling (new-var arguments) is equivalent to call (old-var arguments), without any extra function call.Renzo
"that's a whole extra function call at runtime". Not really. Both bindings point to the same function so it's not wrapping as in (define (de a d) (cons a d))Sylwester

2 Answers

4
votes

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.

2
votes

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! x e) 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