试图理解语言扩展中的要求

joh*_*ohn 7 racket

我正试图在球拍中定义一种新语言,让我们称之为wibble.Wibble将允许加载模块,因此必须将其表单转换为Racket需求表单.但是当我在语言扩展中使用时,我无法获得工作要求.我最终将我的问题追溯到以下奇怪的行为.

这是我的读者重新定义readread-syntax

=== wibble/lang/reader.rkt ===
#lang racket/base

(provide (rename-out (wibble-read read) (wibble-read-syntax read-syntax)))

(define (wibble-read in)
  (wibble-read-syntax #f in))

(define (wibble-read-syntax src in)
  #`(module #,(module-name src) wibble/lang
      #,@(read-all src in)))

(define (module-name src)
  (if (path? src)
      (let-values (((base name dir?) (split-path src)))
        (string->symbol (path->string (path-replace-suffix name #""))))
      'anonymous-module))

(define (read-all src in)
  (let loop ((all '()))
    (let ((obj (read-syntax src in)))
      (if (eof-object? obj)
          (reverse all)
          (loop (cons obj all))))))
Run Code Online (Sandbox Code Playgroud)

这是我简化的语言模块,这将介绍(require racket/base)每个wibble模块

=== wibble/lang.rkt ===
#lang racket/base

(require (for-syntax racket/base))

(provide (rename-out (wibble-module-begin #%module-begin)) #%app #%datum #%top)

(define-syntax wibble-module-begin
  (lambda (stx)
    (syntax-case stx ()
      ((_ x ...) #`(#%module-begin (require #,(datum->syntax stx 'racket/base)) x ...)))))
Run Code Online (Sandbox Code Playgroud)

使用上面的代码然后这个wibble代码'工作',即没有错误

#lang wibble
(cons 1 2)
(cons 3 4)
Run Code Online (Sandbox Code Playgroud)

但以下

#lang wibble
(cons 1 2)
Run Code Online (Sandbox Code Playgroud)

给出错误信息 cons: unbound identifier in module in: cons

真的,我只是在寻找关于发生了什么的解释.我确定这与球拍文档的差异有关(球拍参考3.1)

如果提供单个表单,则在模块开始上下文中部分扩展.如果扩展导致#%plain-module-begin,那么#%plain-module-begin的主体就是模块的主体.如果部分扩展导致任何其他原始形式,则表单使用#%module-begin包装,使用模块体的词法上下文; 此标识符必须由初始模块路径导入绑定,并且其扩展必须生成#%plain-module-begin以提供模块主体.最后,如果提供了多个表单,则使用#%module-begin包装它们,就像单个表单不扩展到#%plain-module-begin的情况一样.

但即便如此,我也不明白为什么单一形式有所不同,这似乎与部分扩张的时机有关,但我不太确定.我也不理解为什么Racket将单个表格视为特殊情况.

顺便说一句,我可以通过稍微修改我的读者来解决问题

(define (wibble-read-syntax src in)
  #`(module #,(module-name src) wibble/lang
      #,@(read-all src in) (void)))
Run Code Online (Sandbox Code Playgroud)

(void)表单进行硬编码意味着我总是有多个表单并且eveything有效.

对不起,很长的帖子,我只是想了解这些东西是如何工作的.

bel*_*lph 5

好吧,我认为我已经解决了。

您的直觉是正确的,因为问题出在单一模块主体的部分扩展时间内。在reader.rkt文件内部,生成一个(module ...)表单。正如您问题中引用的摘录所指出的那样forms ...,由于只有一个,因此部分内容将被特别对待。让我们看一下部分扩展的文档摘录:

作为一种特殊的情况下,当膨胀否则将一个添加#%app#%datum#%top标识符的表达,并且当结合原来是原始#%app#%datum#%top形式,则扩充停止而不添加标识符。

我几乎可以肯定,此时发生的部分扩展会对cons标识符产生某些作用。这是我不确定的那一部分...我的直觉告诉我发生了什么是部分扩展正在尝试查找cons标识符的绑定(由于它是括号的第一部分,因此标识符可以绑定到应扩展的宏,因此需要检查),但无法执行,因此会发脾气。请注意,即使cons没有阶段1(语法扩展时间)绑定,宏扩展器仍然希望标识符具有阶段0(运行时)绑定(此外,这有助于扩展器保持卫生)。因为所有这些部分扩展都发生在(module ...)表单的主体上(这是您在(#%module-begin ...)表单上注入的(#%require ...)表单)cons在扩展过程中没有绑定,因此,我认为扩展失败。

但是,针对您的问题的天真的解决方法是wibble-read-syntax按以下方式重写:

(define (wibble-read-syntax src in)
  (let* ((read-in (read-all src in))
         (in-stx (and (pair? read-in) (car read-in))))
    #`(module #,(module-name src) wibble/lang
        (require #,(datum->syntax in-stx 'racket/base))
        #,@read-in))
Run Code Online (Sandbox Code Playgroud)

然后,您可以(#%require ...)(#%module-begin ...)宏中删除该窗体。

我认为,这不是解决此问题的最佳方法。出于清洁方面的考虑,require以您完成的wibble/lang.rkt方式进行硬编码会使Eli Barzilay和co。哭泣。一种更简单的方法来执行您要执行的操作,方法是将lang.rkt文件更新为如下所示:

=== wibble/lang.rkt ===
#lang racket/base

(require (for-syntax racket/base))

(provide (rename-out (wibble-module-begin #%module-begin))
         (except-out (all-from-out racket/base) #%module-begin #%app #%datum #%top)
     #%app #%datum #%top)

(define-syntax wibble-module-begin
  (lambda (stx)
    (syntax-case stx ()
      ((_ x ...) #`(#%module-begin  x ...)))))
Run Code Online (Sandbox Code Playgroud)

以此约定编写,无需使用任何硬编码(require ...)形式,并防止了诸如您发现的细微错误的出现。如果您对为什么这样做感到困惑,请记住您已经#%module-begin使用此文件提供了标识符,该标识符随后绑定在所有#lang wibble文件中。原则上,以这种方式可以绑定哪些标识符没有限制。如果您想进一步阅读,这是我写过一篇关于该主题的博客文章的一个无耻的自我广告。

希望能对您有所帮助。


stc*_*ang 2

问题在于require(尽管我不确定我 100% 理解所有行为)。

(require X)X从的词法上下文导入绑定#'X#'X这里有 的上下文stx,它是整个#'(module-begin x ...),这不是你想要的上下文。您需要表达式之一的上下文cons,即 s 之一#'x

像这样的东西应该有效:

(define-syntax wibble-module-begin
  (lambda (stx)
    (syntax-case stx ()
      [(_) #'(#%module-begin)]
      [(m x y ...)
       #`(#%module-begin
          (require #,(datum->syntax #'x 'racket/base))
          x y ...)])))
Run Code Online (Sandbox Code Playgroud)

不过,正如 @belph 警告的那样,可能有一种更惯用的方法来完成你想要的事情。

module正如您直觉的那样,您原始程序的行为可能与对单子形式和多子形式的不同处理有关,但我认为“工作”情况可能是一个意外,并且可能是球拍编译器中的错误。