Search code examples
macrosschemeracketdefine-syntaxsyntax-rules

How to control order of Scheme macro expansion?


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.


Solution

  • @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