考虑一种非常简单的参与者语言,其中参与者定义了一些本地状态和一些可以通过向参与者发送消息来调用的方法。在其实现中,参与者的一种此类方法可以转换为定义该方法的形式参数并接受参与者的当前本地状态的函数。调用该方法会返回新的本地状态。
\n\n绑定body中的形式参数没有问题,但是绑定局部状态似乎比较困难。save在下面代码末尾的示例中,在方法主体中,a尽管宏中生成的函数绑定了a(不同的),但 仍将保持未绑定状态。因此,下面的代码示例中的关键点是宏,更具体地说是函数(这是应该发生绑定的地方,这意味着我的程序设计是合理的)aevaluate-bodyMETHODMETHODevaluate-body
有没有一种方法可以卫生地绑定这组任意的自由标识符(目前仅包含a,但实际上它可能是任何东西)?
#lang racket\n\n(require (for-syntax syntax/parse))\n(require racket/stxparam)\n\n(struct actor (local-state methods))\n(struct method (name formal-parameters body))\n\n(define-syntax-parameter local-state-variables #f)\n\n(define-syntax (ACTOR stx)\n (syntax-parse stx\n [(_ (LOCAL_STATE state-variable ...) method:expr ...+)\n #\'(syntax-parameterize ([local-state-variables \'(state-variable ...)])\n ; For the sake of simplicity, an actor is currently a list of message handlers\n (actor\n (make-list (length \'(state-variable ...)) (void))\n (list method ...)))]))\n\n\n(define-syntax (METHOD stx)\n (syntax-parse stx\n [(_ (name:id formal-parameter:id ...) body:expr ...+)\n (with-syntax ([(local-state-variable ...) (syntax-parameter-value #\'local-state-variables)])\n #\'(method\n \'name\n \'(formal-parameter ...)\n (\xce\xbb (formal-parameter ... #:local-state [current-state \'()])\n ; the "a" that will be bound here is different from the free identifier "a" in the body\n (define (evaluate-body local-state-variable ...)\n body ...\n (list local-state-variable ...))\n (apply evaluate-body current-state))))]))\n\n\n(ACTOR (LOCAL_STATE a)\n (METHOD (save new-a)\n ; "a" is an unbound identifier\n (set! a new-a)))\nRun Code Online (Sandbox Code Playgroud)\n
为了使局部状态变量具有正确的词法上下文,您需要将它们存储为标识符,而不是符号。也就是说,在宏的结果中ACTOR,您需要更改syntax-parameterize为:
#\'(syntax-parameterize ([local-state-variables #\'(state-variable ...)])\n #| rest of the template (unchanged)... |#)\nRun Code Online (Sandbox Code Playgroud)\n\nquote注意将/替换\'为syntax/#\'。这将存储标识符及其词汇上下文,而不是作为符号。
下一步是在METHOD宏中正确引入它们。为此,您只需应用syntax-local-introduce语法参数的值,这会将宏引入范围添加到标识符中。也可以替换with-syntax为syntax-parse\xe2\x80\x99s#:with子句以稍微简化事情,因此整体宏变为:
(define-syntax (METHOD stx)\n (syntax-parse stx\n [(_ (name:id formal-parameter:id ...) body:expr ...+)\n #:with (local-state-variable ...)\n (syntax-local-introduce (syntax-parameter-value #\'local-state-variables))\n #\'(method #| rest of the template (unchanged)... |#)]))\nRun Code Online (Sandbox Code Playgroud)\n\n这会起作用。
\n\n这里需要的原因syntax-local-introduce可能有点令人困惑,但最直观的思考方式是考虑 Racket 当前使用的 \xe2\x80\x9d 卫生模型的 \xe2\x80\x9csets 范围\xe2\x80\x9d 卫生模型。为了使宏引入的绑定不与用户定义的绑定冲突,语法转换器返回的每条语法都附加了一个新的作用域,该作用域永远不会附加到用户编写的任何内容。当然,结果中的一些语法是用户提供的语法,因此宏扩展器需要确保它不会将新范围附加到这些语法对象。
一般来说,不可能找出应考虑由用户提供哪些语法对象,因为宏作者可以 \xe2\x80\x9cbend\xe2\x80\x9d 卫生并从其他对象创建新的语法对象那些。幸运的是,解决方案简单而优雅:只需将宏引入范围附加到用户提供的所有语法对象,然后将它们交给宏,然后翻转结果中所有语法片段的范围。这样,用户提供的语法对象在翻转发生后将不具有宏引入作用域。
\n\n该syntax-local-introduce功能允许您手动翻转这个特殊范围。在这种情况下,由于值local-state-variables应该被视为宏的输入,但它不会由宏扩展器自动给出宏引入范围(因为它不是 xe2x80x99t 的直接输入)宏),您必须自己添加范围。这样,宏扩展器将在宏扩展后删除作用域,并且标识符将以正确的词法上下文结束。