Racket中的模块元语言

8

我正在尝试用Racket编写一个模块元语言mylang,它接受第二种语言并将修改后的主体传递给它,使之满足以下条件:

(module foo mylang typed/racket body)

等同于:

(module foo typed/racket transformed-body)

其中typed/racket部分可以被任何其他模块语言替换,当然。

我尝试了一个简单版本,保留了主体不变。它在命令行上运行良好,但在DrRacket中运行时会出现以下错误:

/usr/share/racket/pkgs/typed-racket-lib/typed-racket/typecheck/tc-toplevel.rkt:479:30: require: namespace mismatch;
 reference to a module that is not available
  reference phase: 1
  referenced module: "/usr/share/racket/pkgs/typed-racket-lib/typed-racket/env/env-req.rkt"
  referenced phase level: 0 in: add-mod!

这是整个代码:

#lang racket

(module mylang racket
  (provide (rename-out [-#%module-begin #%module-begin]))
  (require (for-syntax syntax/strip-context))
  (define-syntax (-#%module-begin stx)
    (syntax-case stx ()
      [(_ lng . rest)
       (let ([lng-sym (syntax-e #'lng)])
         (namespace-require `(for-meta -1 ,lng-sym))
         (with-syntax ([mb (namespace-symbol->identifier '#%module-begin)])
           #`(mb . #,(replace-context #'mb #'rest))))])))

(module foo (submod ".." mylang) typed/racket/base
  (ann (+ 1) Number))

(require 'foo)

要求(即我宁愿避免的解决方案):

  • mylang模块内添加(require (only-in typed/racket))可以使其工作,但我对通用解决方案感兴趣,其中mylang不需要知道typed/racket(即如果有人添加了一个新的语言foo,那么mylang应该可以直接使用它)。
  • 此外,我不感兴趣的方法是声明一个子模块并立即require和重新provide它,就像这里做的那样,因为这会改变实际模块的路径(例如,maintest将失去其特殊行为)。

    这也会在编译时变慢,因为子模块被访问和/或实例化更多次(可以通过编写(begin-for-syntax (displayln 'here))来看到这一点,并且对于大型的typed/racket程序有明显的影响。

  • 如果DrRacket中的箭头适用于委托给语言提供的内置函数,例如在上面的示例中从ann+Numbertyped/racket/base的箭头,则会获得额外的奖励分数。


我倾向于认为这是Typed Racket中的一个错误--但我对Typed Racket的内部了解不足,无法确定。@sam-tobin-hochstadt - soegaard
注意:我仅基于奇怪的错误位置进行推断。 - soegaard
1个回答

4

有一件事情你可以做,我认为这不违反你的要求:将它放在一个模块中,完全展开该模块,然后匹配 #%plain-module-begin 并插入一个 require 语句。

#lang racket

(module mylang racket
  (provide (rename-out [-#%module-begin #%module-begin]))
  (define-syntax (-#%module-begin stx)
    (syntax-case stx ()
      [(_ lng . rest)
       (with-syntax ([#%module-begin (datum->syntax #f '#%module-begin)])
         ;; put the code in a module form, and fully expand that module
         (define mod-stx
           (local-expand
            #'(module ignored lng (#%module-begin . rest))
            'top-level
            (list)))
         ;; pattern-match on the #%plain-module-begin form to insert a require
         (syntax-case mod-stx (module #%plain-module-begin)
           [(module _ lng (#%plain-module-begin . mod-body))
            #'(#%plain-module-begin
                (#%require lng)
                .
                mod-body)]))])))

;; Yay the check syntax arrows work!
(module foo (submod ".." mylang) typed/racket/base
  (ann (+ 1) Number))

(require 'foo)

如果您想以某种方式转换主体,可以在展开之前或之后执行操作。

插入额外的(#%require lng)所需的模式匹配是必要的,因为在可用lng的上下文中扩展模块体不足够。将mod-body代码移出module表单意味着绑定将引用lng,但在运行时lng将不可用。这就是为什么没有它我会得到require: namespace mismatch; reference to a module that is not available错误的原因,也是为什么需要在扩展后添加它的原因。

评论更新

然而,正如@GeorgesDupéron在评论中指出的那样,这引入了另一个问题。如果lng提供了标识符x,并且使用它的模块导入了不同的x,则会出现不应有的导入冲突。要解决这个问题,要求语句应在相对于模块语言的“嵌套作用域”中,以便它们可以遮蔽像x这样的标识符。

@GeorgesDupéron在racket users list的电子邮件中发现了这个问题的解决方案,使用(make-syntax-introducer)mod-body上生成嵌套作用域。

(module mylang racket
  (provide (rename-out [-#%module-begin #%module-begin]))
  (define-syntax (-#%module-begin stx)
    (syntax-case stx ()
      [(_ lng . rest)
       (with-syntax ([#%module-begin (datum->syntax #f '#%module-begin)])
         ;; put the code in a module form, and fully expand that module
         (define mod-stx
           (local-expand
            #'(module ignored lng (#%module-begin . rest))
            'top-level
            (list)))
         ;; pattern-match on the #%plain-module-begin form to insert a require
         (syntax-case mod-stx (module #%plain-module-begin)
           [(module _ lng (#%plain-module-begin . mod-body))
            #`(#%plain-module-begin
                (#%require lng)
                .
                #,((make-syntax-introducer) #'mod-body))]))])))

这是一个不错的解决方案! - soegaard
1
再次感谢!我遇到了一个微妙的边角情况:如果 foo 需要一个模块(例如 type-expander),它会掩盖 typed/racket/base 导出的一些绑定,这将与插入的 (#%require lng) 冲突(因为模块语言绑定可以被 (require type-expander) 掩盖,但是 require 的绑定不能被另一个 require 掩盖)。将插入的 (#%require lng) 更改为 (#%require (only lng)) 似乎足以使模块可用,而不实际导入可能与其他 require 模块冲突的任何绑定。 - Suzanne Soy
我根据您在列表中的电子邮件再次进行了更改。 - Alex Knauth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接