我正试图在球拍中定义一种新语言,让我们称之为wibble.Wibble将允许加载模块,因此必须将其表单转换为Racket需求表单.但是当我在语言扩展中使用时,我无法获得工作要求.我最终将我的问题追溯到以下奇怪的行为.
这是我的读者重新定义read和read-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有效.
对不起,很长的帖子,我只是想了解这些东西是如何工作的.
好吧,我认为我已经解决了。
您的直觉是正确的,因为问题出在单一模块主体的部分扩展时间内。在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文件中。原则上,以这种方式可以绑定哪些标识符没有限制。如果您想进一步阅读,这是我写过一篇关于该主题的博客文章的一个无耻的自我广告。
希望能对您有所帮助。
问题在于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正如您直觉的那样,您原始程序的行为可能与对单子形式和多子形式的不同处理有关,但我认为“工作”情况可能是一个意外,并且可能是球拍编译器中的错误。