【问题标题】:Binding a self reference via macros通过宏绑定自引用
【发布时间】:2017-01-02 18:23:45
【问题描述】:

我正在处理的项目定义了一些复杂的结构,它们接收消息并在它们自己的线程中运行。这些结构是用户定义的,并通过宏转换为线程和运行时的东西。粗略地说,我们可以说一个复杂的结构由一些实现逻辑的行为和一个产生行为实例的过程组成。在下面的代码中,我大大简化了这种情况,create-thread-behaviour 宏定义的行为是一个简单的 thunk,可以通过spawn 宏产生。我想实现一个行为(一个实例)的能力,通过self 参数向自己发送消息,该参数将绑定到(current-thread)(〜运行该行为的线程)。

我曾尝试使用syntax-parameterize 来安装某些东西,但由于某种原因无法使其正常工作。下面的代码实现了一个简单的应用程序,它应该阐明我想要实现的目标——特别感兴趣的是(未实现的)<self> 对底部的引用。

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

(define-syntax (create-thread-behaviour stx)
  (syntax-parse stx
    [(_ body:expr ...+)
     #'(λ () body ...)]))

(define-syntax (spawn stx)
  (syntax-parse stx
    [(_ behaviour:id)
     #'(thread behaviour)]))


(define behaviour
  (create-thread-behaviour
   (let loop ()
     (define message (thread-receive))
     (printf "message: ~a~n" message)
     (thread-send <self> "And this is crazy.")
     (loop))))

(define instance (spawn behaviour))
(thread-send instance "Hey I just met you")

所以我尝试的带有语法参数的东西如下,它引发了自定义的“只能用于行为”错误。我知道我之前正确使用过语法参数,但也许我只是关注这个问题太久了。

(require racket/stxparam)

(define-syntax-parameter self
  (lambda (stx) (raise-syntax-error (syntax-e stx) "can only be used in a behaviour")))

(define-syntax (spawn stx)
  (syntax-parse stx
    [(_ behaviour:id)
     #'(thread
        (lambda ()
          (syntax-parameterize ([self #'(current-thread)])
            (behaviour))))]))

【问题讨论】:

  • 您能否发布一个使用syntax-parameterize 的(非工作)尝试?这绝对是适合这项工作的工具,而且指出你缺少的东西可能比重新实现所有东西更有帮助。
  • 你是对的!给我一分钟:)

标签: racket


【解决方案1】:

你说得对,语法参数似乎是适合这里工作的工具。但是,在您使用它们时有两个问题会导致该问题。让我们一个接一个。

首先,语法参数在语义上只是语法转换器,正如您最初使用define-syntax-parameter 所见,它将语法参数绑定到函数。相反,您对syntax-parameterize 的使用将语法参数绑定到一段语法,这是错误的。相反,您还需要将其绑定到语法转换器。

实现您正在寻找的行为的一种简单方法是使用来自syntax/transformermake-variable-like-transformer 函数,该函数生成一个语法转换器,顾名思义,它的行为就像一个变量。不过,更一般地说,它实际上会生成一个行为类似于 表达式 的转换器,(current-thread) 就是这样。因此,您对syntax-parameterize 的使用实际上应该如下所示:

(require (for-syntax syntax/transformer))

(syntax-parameterize ([self (make-variable-like-transformer #'(current-thread))])
  (behaviour))

这将避免在参数化后尝试使用 self 时出现“语法错误”错误。

但是,您的代码中还有另一个问题,即当语法参数不能这样工作时,它似乎使用了像普通的非语法参数一样的语法参数。普通参数实际上是动态作用域,因此使用syntax-parameterize 包裹(behavior) 将在调用behavior动态范围 内调整self

但是,语法参数不是这样工作的。事实上,他们不能:Racket 在语法上是一种词法范围的语言,所以你真的不能有一个动态的语法绑定:所有的语法转换器都是在编译时扩展的,所以在调用的动态范围内调整绑定是不可能的.语法参数完全是词法范围的,它们只是卫生地调整特定范围内的绑定。从这个意义上说,它们确实就像let,只是它们调整现有绑定而不是生成新绑定

考虑到这一点,很明显将syntax-parameterize 形式放在spawn 中实际上是行不通的,因为behavior 是在spawn 之外定义的。您可以将 syntax-parameterize 的用法移动到 create-thread-behavior,但现在还有另一个问题,那就是这不起作用:

(define (behavior-impl)
  (define message (thread-receive))
  (printf "message: ~a~n" message)
  (thread-send self "And this is crazy.")
  (behavior-impl))

(define behaviour
  (create-thread-behavior
   (behavior-impl)))

现在,self 再次在 syntax-parameterize 的词汇范围之外使用,因此它不会被绑定。

您提到这是您实际操作的简化示例,因此您的实际示例可能需要更复杂的解决方案。如果是这样,您可能只需要要求self 仅绑定在create-thread-behavior 的词汇范围内。但是,您当前对 self 的使用非常简单,事实上,它从未改变:它始终 (current-thread)。出于这个原因,您实际上可以完全放弃语法参数并直接定义self

(define-syntax self (make-variable-like-transformer #'(current-thread)))

现在self 将作为对参数值current-thread 的可变外观引用在任何地方工作。这可能是您真正想要的,因为它允许 self 的值是真正动态范围的(因为它使用运行时参数,而不是语法参数),但它仍然使它看起来像一个变量而不是一个函数。

【讨论】:

  • 这是一个了不起的答案!你也不是第一次给我一个惊人的答案了!非常感谢你!在实际代码中self的使用几乎和示例一样简单,只是涉及到更多的逻辑,包括一些无法定义self的上下文。禁止在create-thread-behaviour 的词汇上下文之外使用self 是一项艰巨的任务吗?一种解决方案可能是使用常规参数来确定self 是否在正确的上下文中使用(如果是,则返回当前线程,否则引发错误)。我想它也可以在编译时强制执行?
  • @Sam 如果您想在create-thread-behavior 的词法范围之外禁止self,您可以使用语法参数并将syntax-parameterize 包裹在create-thread-behavior 的扩展主体周围,与您已经在做的非常相似。如果您想在create-thread-behavior动态 范围之外禁止self,则必须使用普通参数并进行运行时检查,因为在编译时无法知道动态范围.
猜你喜欢
  • 2014-07-20
  • 2020-07-02
  • 2016-01-12
  • 2020-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-08
  • 1970-01-01
相关资源
最近更新 更多