通过宏绑定自引用

时间:2017-01-02 18:23:45

标签: racket

我正在研究的项目定义了一些接收消息并在自己的线程中运行的复杂结构。结构是用户定义的,并通过宏转换为线程和运行时的东西。粗略地说,我们可以说复杂的结构包含一些实现逻辑的行为,以及一个产生行为实例的过程。在下面的代码中,我大大简化了这种情况,其中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))))]))

1 个答案:

答案 0 :(得分:5)

你是对的,语法参数似乎是这里工作的正确工具。但是,您在使用它们时存在两个问题,这些问题导致了问题。我们一次拿一个。

首先,语法参数在语义上只是语法变换器,您可以通过初始使用define-syntax-parameter来看到,它将语法参数绑定到函数。相反,您对syntax-parameterize的使用会将语法参数绑定到一段语法,这是错误的。相反,您还需要将其绑定到语法转换器。

实现您正在寻找的行为的一种简单方法是使用make-variable-like-transformer中的syntax/transformer函数,该函数生成一个语法转换器,顾名思义,它的行为类似于变量。但更一般地说,它实际上产生的行为类似于表达式的变换器,(current-thread)。出于这个原因,您对syntax-parameterize的使用应该看起来像这样:

(require (for-syntax syntax/transformer))

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

这样可以避免在参数化后使用self时出现“语法错误”错误。

但是,您的代码中还存在另一个问题,即当语法参数不起作用时,它似乎使用类似普通的非语法参数的语法参数。正常参数实际上是动态范围的,因此使用syntax-parameterize来包裹(behavior)会调整{em>动态范围内的self致电behavior

但是,语法参数不起作用。事实上,它们不能: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)))

现在,再次selfsyntax-parameterize的词汇范围之外使用,因此它不受约束。

您已经提到过这是您正在做的事情的简化示例,因此您的真实示例可能需要更复杂的解决方案。如果是这样,您可能只需要要求self仅限于create-thread-behavior的词汇范围内。但是,您目前使用self非常简单,事实上,它永远不会改变:它总是 (current-thread)。因此,您实际上可以完全抛弃语法参数并直接定义self

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

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