【问题标题】:trying to understand require in language extension试图理解语言扩展中的需求
【发布时间】:2015-10-15 22:39:26
【问题描述】:

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

这是我的阅读器,它重新定义了 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))))))

这是我大大简化的语言模块,它将(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 ...)))))

使用上面的代码,那么这个 wibble 代码“有效”,即没有错误

#lang wibble
(cons 1 2)
(cons 3 4)

但以下

#lang wibble
(cons 1 2)

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

真的,我只是在寻找关于发生了什么的解释。我确定差异与球拍文档(Racket Reference 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)))

(void) 表单进行硬编码意味着我总是有多个表单并且一切正常。

抱歉,这篇文章太长了,我只是想了解一下这些东西的工作原理。

【问题讨论】:

    标签: racket


    【解决方案1】:

    好吧,我想我已经想通了。

    您的直觉是正确的,因为问题在于单一形式模块体的部分扩展的时间。在您的 reader.rkt 文件中,您会生成一个 (module ...) 表单。正如您的问题所引用的摘录所述,这部分的forms ... 然后被特殊处理,因为只有一个。让我们看一下部分扩展文档的摘录:

    作为一种特殊情况,当扩展会在表达式中添加#%app#%datum#%top 标识符,并且当绑定结果是原始#%app#%datum#%top 形式,然后扩展停止而不添加标识符。

    我几乎可以肯定,此时发生的部分扩展对cons 标识符有影响。这是我仍然不确定的部分......我的直觉告诉我,正在发生的事情是部分扩展正在尝试找到 cons 标识符的绑定(因为它是括号的第一部分,标识符可以绑定到应该扩展的宏,因此需要检查)但不能,所以它会发脾气。请注意,即使cons 没有第 1 阶段(语法扩展时间)绑定,宏扩展器仍然希望标识符有第 0 阶段(运行时)绑定(除其他外,这有助于扩展器保持卫生)。因为所有这些部分扩展都发生在您的(module ...) 表单的主体上(在您注入(#%module-begin ...) 表单的(#%module-begin ...) 表单之前完成),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))
    

    然后,您可以从 (#%module-begin ...) 宏中删除 (#%require ...) 表单。

    在我看来,这不是解决问题的最佳方法。出于清洁的考虑,像在wibble/lang.rkt 中所做的那样,以require 的形式硬编码将make Eli Barzilay and co. cry。一个更简单的方法来做你想做的事是将你的 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 ...)))))
    

    按照这种约定进行编写无需任何硬编码的(require ...) 表单,并防止出现像您发现的那样的细微错误。如果您对它的工作原理感到困惑,请记住您已经使用此文件提供了#%module-begin 标识符,该标识符随后绑定到所有#lang wibble 文件中。原则上,您可以以这种方式绑定的标识符没有限制。如果您想进一步阅读,这里有一个 blog post I wrote 的无耻自我广告,稍后再讨论这个主题。

    希望我能帮上忙。

    【讨论】:

    • 感谢您的回答。不幸的是,硬编码的要求只是为了让我处理这个问题。在实际情况下,我会将 wibble 代码扩展为需要表单。所以你的第二个修复不起作用,因为我要导入的 Racket 模块取决于编写的 wibble 代码。你第一次修复的东西可能会起作用,但它很丑陋。我认为我最好的选择仍然是完全避免模块案例中的一种形式。我不认为我正在尝试做的事情如此不寻常,所以我很好奇其他类似的实现如何处理这个问题。
    • @john 为什么不在从wibble-read-syntax 返回(module ...) 表单之前执行任何需要的扩展来推断需要什么?这对您的用例不可行吗?
    【解决方案2】:

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

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

    这样的事情应该可以工作:

    (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 ...)])))
    

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

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

    【讨论】:

    • 嗯,我想你已经明白了。我不确定我是否会得到那个答案。语法相关的东西很难。谢谢。
    猜你喜欢
    • 2021-12-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多