动态绑定函数体

时间:2016-12-14 10:09:10

标签: racket

考虑一种非常简单的actor语言,其中actor定义了一些本地状态,以及一些可以通过向actor发送消息来调用的方法。在其实现中,可以将actor的这种方法转换为定义方法的形式参数并接受actor的当前本地状态的函数。调用该方法将返回新的本地状态。

绑定正文中的形式参数没有问题,但绑定本地状态似乎更难。在下面代码末尾的示例中,save方法的正文中a将保持未绑定状态,尽管绑定了a(不同的a)通过evaluate-body宏中生成的METHOD函数。因此,下面的代码示例中的关键点是METHOD宏,更具体地说是evaluate-body函数(这是绑定应该发生的地方,这意味着我的程序设计是合理的)

有没有办法卫生地绑定这组任意一组免费标识符(目前只包含a,但实际上可能是什么)?

#lang racket

(require (for-syntax syntax/parse))
(require racket/stxparam)

(struct actor (local-state methods))
(struct method (name formal-parameters body))

(define-syntax-parameter local-state-variables #f)

(define-syntax (ACTOR stx)
  (syntax-parse stx
    [(_ (LOCAL_STATE state-variable ...) method:expr ...+)
     #'(syntax-parameterize ([local-state-variables '(state-variable ...)])
         ; For the sake of simplicity, an actor is currently a list of message handlers
         (actor
          (make-list (length '(state-variable ...)) (void))
          (list method ...)))]))


(define-syntax (METHOD stx)
  (syntax-parse stx
    [(_ (name:id formal-parameter:id ...) body:expr ...+)
     (with-syntax ([(local-state-variable ...) (syntax-parameter-value #'local-state-variables)])
       #'(method
          'name
          '(formal-parameter ...)
          (λ (formal-parameter ... #:local-state [current-state '()])
            ; the "a" that will be bound here is different from the free identifier "a" in the body
            (define (evaluate-body local-state-variable ...)
              body ...
              (list local-state-variable ...))
            (apply evaluate-body current-state))))]))


(ACTOR (LOCAL_STATE a)
       (METHOD (save new-a)
               ; "a" is an unbound identifier
               (set! a new-a)))

1 个答案:

答案 0 :(得分:2)

为了使本地状态变量具有适当的词法上下文,您需要将它们存储为标识符,而不是符号。也就是说,在ACTOR宏的结果中,您需要将syntax-parameterize更改为:

#'(syntax-parameterize ([local-state-variables #'(state-variable ...)])
    #| rest of the template (unchanged)... |#)

请注意将quote / '替换为syntax / #'。这将使用词汇上下文而不是符号来存储标识符。

下一步是在METHOD宏中正确引入它们。为此,您只需将syntax-local-introduce应用于语法参数的值,这将将宏引入范围添加到标识符。您还可以将with-syntax替换为syntax-parse的{​​{1}}子句,以便稍微简化一下,因此整体宏会变为:

#:with

这样可行。

这里需要(define-syntax (METHOD stx) (syntax-parse stx [(_ (name:id formal-parameter:id ...) body:expr ...+) #:with (local-state-variable ...) (syntax-local-introduce (syntax-parameter-value #'local-state-variables)) #'(method #| rest of the template (unchanged)... |#)])) 的原因可能有点令人困惑,但最直观的思考方式是考虑Racket目前使用的“范围集”卫生模型。为了使宏引入的绑定不与用户定义的绑定冲突,语法转换器返回的每个语法都附加了一个新的作用域,这个作用域永远不会附加到用户编写的任何内容上。当然,结果中的一些语法是用户提供的语法,因此宏扩展器需要确保它不会将新范围附加到那些语法对象。

一般来说,不可能确定用户应该考虑哪些语法对象,因为宏作者可以“弯曲”卫生并从其他语法创建新的语法对象。幸运的是,解决方案简单而优雅:只需将宏引入范围附加到用户提供的所有语法对象将它们交给宏,然后翻转范围在结果中的所有语法片段上。这样,在翻转发生后,用户提供的语法对象将不具有宏引入范围。

syntax-local-introduce功能可让您手动翻转此特殊范围。在这种情况下,由于syntax-local-introduce的值应该被视为宏的输入,但宏扩展器不会自动给出宏引入范围(因为它不是宏的直接输入) ,你必须自己添加范围。这样,宏扩展器将在宏扩展后删除范围,标识符将以适当的词汇上下文结束。