How to control order of Scheme macro expansion?

2019-07-10 04:04发布

问题:

I'm working with the Racket macro extension syntax-id-rules, that some other Scheme implementations provide under the name identifier-syntax. These let you specify macro expansions that will happen even when the defined identifier isn't in head position. So for example:

(define hidden #f)
(define-syntax proxy
  (syntax-id-rules (set!)
    [(set! proxy v) (set! hidden v)]
    [proxy hidden]))

will set up the identifier proxy to be a proxy for hidden. This is a useless example, but it illustrates the usage.

I find myself in a situation where I want a global ordinary macro, let's call it foo, that I want to override in some cases where I'm using an identifier macro like proxy. That is, I want to be able to do something like this:

(define-syntax foo
  (syntax-rules ()
    [(foo arg ...) 'default]))

(define hidden #f)
(define-syntax proxy
  (syntax-id-rules (foo set!)
    [(foo proxy arg ...) 'special]
    [(set! proxy v) (set! hidden v)]
    [proxy hidden]))

(foo proxy) ; should return 'special

But in fact the last line returns 'default, because the foo macro gets expanded before the proxy one.

Any ideas how I might achieve something along these lines, but with the proxy identifier macro overriding the default macro definition for foo? I'm not committed to the above architecture specifically.

Added: This isn't for any real-world usage, but part of a demonstration of a theoretical point in formal semantics.

回答1:

@soegaard explained it perfectly. You can't do what you want directly without modifying the macro expander.

To extend @soegaard's answer, here is a way to simulate what you are asking for. It essentially does a "double-dispatch" macro expansion. As soegaard noted though, there's probably a more idiomatic way to achieve what you want, depending on your goals.

#lang racket
(require (for-syntax syntax/parse))

(begin-for-syntax
  (define (special-condition? id)
    (and (identifier? id)
         (regexp-match #rx"^p" ; starts with "p"
                       (symbol->string (syntax->datum id))))))

(define-syntax foo
  (syntax-parser
    [(_ special-case arg ...)
     #:when (special-condition? #'special-case)
     #'(special-case 'hidden-special-case-tag arg ...)]
    ; else
    [(_ arg ...) #''default]))

(define hidden #f)
(define-syntax proxy
  (syntax-id-rules (quote set!)
    [(proxy (quote hidden-special-case-tag) arg ...) 'special]
    [(set! proxy v) (set! hidden v)]
    [(proxy arg ...) 'other]
    [proxy hidden]))

(foo non-proxy) ; => 'default
(foo proxy) ; => 'special
(proxy) ; => 'other
proxy ; => #f
(set! proxy #t)
proxy ; => #t


回答2:

It seems to me that you need to find an alternative strategy. Maybe we can find a solution if you provide more details of the situation you want to use this in.

Anyways, here is why your strategy don't work. When you write

(define-syntax proxy ...)

you associate a syntax-transformer with the identifier proxy. That transformer is called by the expander when it sees either (proxy ...), (set! proxy ...), or proxy.

In order to control what (foo proxy arg ...) expands to you need to specify it in the syntax-transformer associated with foo.

Now depending on the situation there might be tricks that can be played.

For example one could imagine wrapping your program in a new form, that rewrites (foo proxy arg ...) into (proxy 'was-a-foo-originally arg ...) and then let the syntax-transformer for proxy handle the rest.

The easy solution is to move the handling of (foo proxy arg ...) into the transformer for foo, but you specifically ask for a solution where foo isn't changed.