如何控制Scheme宏展开的顺序?
How to control order of Scheme macro expansion?
我正在使用 Racket 宏扩展 syntax-id-rules
,其他一些 Scheme 实现以名称 identifier-syntax
提供。这些允许您指定即使定义的标识符不在头部位置也会发生的宏扩展。例如:
(define hidden #f)
(define-syntax proxy
(syntax-id-rules (set!)
[(set! proxy v) (set! hidden v)]
[proxy hidden]))
将设置标识符 proxy
作为 hidden
的代理。这是一个无用的例子,但它说明了用法。
我发现自己处于这样一种情况,我想要一个全局普通宏,我们称之为 foo
,在某些情况下我想覆盖它,我正在使用像 proxy
这样的标识符宏.也就是说,我希望能够做这样的事情:
(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
但实际上最后一行 returns 'default
,因为 foo
宏在 proxy
宏之前展开。
有什么想法我可以按照这些思路实现某些东西,但使用 proxy
标识符宏覆盖 foo
的默认宏定义?我不专门致力于上述架构。
补充:这不是用于任何现实世界的用法,而是形式语义学理论点演示的一部分。
在我看来,您需要找到替代策略。如果您提供更多关于您想要使用它的情况的详细信息,也许我们可以找到解决方案。
无论如何,这就是您的策略不起作用的原因。当你写
(define-syntax proxy ...)
您将语法转换器与标识符 proxy
相关联。当扩展器看到 (proxy ...)
、(set! proxy ...)
或 proxy
时,它会调用该转换器。
为了控制 (foo proxy arg ...)
扩展到的内容,您需要在与 foo
关联的语法转换器中指定它。
现在看情况还是有招数可以玩的
例如,可以想象用一种新形式包装您的程序,将 (foo proxy arg ...)
重写为 (proxy 'was-a-foo-originally arg ...)
,然后让 proxy
的语法转换器处理其余部分。
简单的解决方案是将 (foo proxy arg ...)
的处理移至 foo
的转换器中,但您特别要求 foo
未更改的解决方案。
@soegaard 解释得很完美。不修改宏扩展器就不能直接为所欲为。
为了扩展@soegaard 的回答,这里有一种方法可以模拟您的要求。它本质上做了一个 "double-dispatch" 宏扩展。正如 soegaard 指出的那样,根据您的目标,可能有更惯用的方法来实现您想要的。
#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
我正在使用 Racket 宏扩展 syntax-id-rules
,其他一些 Scheme 实现以名称 identifier-syntax
提供。这些允许您指定即使定义的标识符不在头部位置也会发生的宏扩展。例如:
(define hidden #f)
(define-syntax proxy
(syntax-id-rules (set!)
[(set! proxy v) (set! hidden v)]
[proxy hidden]))
将设置标识符 proxy
作为 hidden
的代理。这是一个无用的例子,但它说明了用法。
我发现自己处于这样一种情况,我想要一个全局普通宏,我们称之为 foo
,在某些情况下我想覆盖它,我正在使用像 proxy
这样的标识符宏.也就是说,我希望能够做这样的事情:
(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
但实际上最后一行 returns 'default
,因为 foo
宏在 proxy
宏之前展开。
有什么想法我可以按照这些思路实现某些东西,但使用 proxy
标识符宏覆盖 foo
的默认宏定义?我不专门致力于上述架构。
补充:这不是用于任何现实世界的用法,而是形式语义学理论点演示的一部分。
在我看来,您需要找到替代策略。如果您提供更多关于您想要使用它的情况的详细信息,也许我们可以找到解决方案。
无论如何,这就是您的策略不起作用的原因。当你写
(define-syntax proxy ...)
您将语法转换器与标识符 proxy
相关联。当扩展器看到 (proxy ...)
、(set! proxy ...)
或 proxy
时,它会调用该转换器。
为了控制 (foo proxy arg ...)
扩展到的内容,您需要在与 foo
关联的语法转换器中指定它。
现在看情况还是有招数可以玩的
例如,可以想象用一种新形式包装您的程序,将 (foo proxy arg ...)
重写为 (proxy 'was-a-foo-originally arg ...)
,然后让 proxy
的语法转换器处理其余部分。
简单的解决方案是将 (foo proxy arg ...)
的处理移至 foo
的转换器中,但您特别要求 foo
未更改的解决方案。
@soegaard 解释得很完美。不修改宏扩展器就不能直接为所欲为。
为了扩展@soegaard 的回答,这里有一种方法可以模拟您的要求。它本质上做了一个 "double-dispatch" 宏扩展。正如 soegaard 指出的那样,根据您的目标,可能有更惯用的方法来实现您想要的。
#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