racket - 试图理解语言扩展中的需求

标签 racket

我正在尝试在 Racket 中定义一种新语言,我们称之为 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/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 文档(Racket Reference 3.1)有关

If a single form is provided, then it is partially expanded in a module-begin context. If the expansion leads to #%plain-module-begin, then the body of the #%plain-module-begin is the body of the module. If partial expansion leads to any other primitive form, then the form is wrapped with #%module-begin using the lexical context of the module body; this identifier must be bound by the initial module-path import, and its expansion must produce a #%plain-module-begin to supply the module body. Finally, if multiple forms are provided, they are wrapped with #%module-begin, as in the case where a single form does not expand to #%plain-module-begin.



但即使如此,我也不明白为什么只有一个表格会有什么不同,这似乎与部分扩展的时间有关,但我不太确定。我也不明白为什么 Racket 将单个表单视为特例。

顺便说一句,我可以通过对我的读者稍作修改来解决这个问题
(define (wibble-read-syntax src in)
  #`(module #,(module-name src) wibble/lang
      #,@(read-all src in) (void)))

硬编码 (void)形式意味着我总是有不止一种形式,而且一切都有效。

抱歉,这篇文章很长,我只是想了解一下这些东西是如何工作的。

最佳答案

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

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

As a special case, when expansion would otherwise add an #%app, #%datum, or #%top identifier to an expression, and when the binding turns out to be the primitive #%app, #%datum, or #%top form, then expansion stops without adding the identifier.



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

然后您可以删除 (#%require ...)来自您的 (#%module-begin ...) 的表格宏。

但是,在我看来,这不是解决问题的最佳方法。出于清洁考虑,在 require 中进行硬编码就像你在 wibble/lang.rkt 中所做的那样会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使用此文件的标识符,随后将其绑定(bind)到所有 #lang wibble文件。原则上,您可以以这种方式绑定(bind)的标识符没有限制。如果您想进一步阅读,这里有一个 blog post I wrote 的无耻自我广告。一会儿回到这个主题。

我希望我有所帮助。

关于racket - 试图理解语言扩展中的需求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31624384/

相关文章:

sockets - Racket 回声服务器/客户端挂起

list - 如何迭代列表中的每个元素而不删除方案中的元素

racket - plt-redex:免费捕获避免替换?

functional-programming - Racket - 如何为列表分配长度

lisp - “car: contract violation expected: pair?” 插入树时

lambda - ((lambda () )) 的简写

functional-programming - 如何以功能方式实现 'state-driven' 程序?

function - 为匿名函数分配名称与定义它们 "normally"

dictionary - Racket : (map #(procedure) (list)) 中的 Clojure 等效项

functional-programming - letrec 有什么好处?