通过宏绑定自引用

Sam*_*Sam 5 racket

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

所以我尝试的语法参数是以下内容,它引发了自定义的"只能在行为中使用"错误.我知道我之前已经正确使用了语法参数,但也许我一直在研究这个问题太久了.

(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))))]))
Run Code Online (Sandbox Code Playgroud)

Ale*_*ing 5

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

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

实现您正在寻找的行为的一种简单方法是使用make-variable-like-transformer函数from syntax/transformer,这使得语法转换器就像名称所暗示的那样,表现得像一个变量.更一般地,虽然,它实际上产生行为类似的变压器表达,这(current-thread)是.出于这个原因,你的使用syntax-parameterize应该看起来像这样:

(require (for-syntax syntax/transformer))

(syntax-parameterize ([self (make-variable-like-transformer #'(current-thread))])
  (behaviour))
Run Code Online (Sandbox Code Playgroud)

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

但是,您的代码中存在另一个问题,即当语法参数不起作用时,它似乎使用类似普通的非语法参数的语法参数.正常参数是有效动态范围的,因此使用syntax-parameterize(behavior)行将在调用self动态范围内进行调整behavior.

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

考虑到这一点,很明显,将syntax-parameterize表单放入其中spawn并不能真正起作用,因为它behavior是在词法之外定义的spawn.您可以将使用转移syntax-parameterizecreate-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)))
Run Code Online (Sandbox Code Playgroud)

现在,再一次,self在词汇范围之外使用syntax-parameterize,所以它不会受到约束.

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

(define-syntax self (make-variable-like-transformer #'(current-thread)))
Run Code Online (Sandbox Code Playgroud)

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