8

我正在尝试在 Racket 中编写一个模块 meta-language 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)

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

  • (require (only-in typed/racket))在模块内部添加一个mylang可以使这项工作,但我对一个通用的解决方案感兴趣,mylang根本不需要知道typed/racket(即如果有人添加了一种新语言foo,那么mylang应该开箱即用)。
  • 此外,我对声明子模块并立即require重新声明子模块的技巧不感兴趣provide就像这里所做的那样,因为这会改变到实际模块的路径(例如,因此会main失去test它们的特殊行为)。

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

  • 如果 DrRacket 中的箭头适用于委托语言提供的内置功能,例如在上面的示例中具有来自和to的箭头ann,则可以加分。+Numbertyped/racket/base

4

1 回答 1

4

您可以做的一件事,我认为不会违反您的要求,将其放入一个模块中,完全扩展该模块,然后匹配#%plain-module-begin以插入一个要求。

#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,那么将在不应该出现的地方出现导入冲突。Require 行应该在模块语言的“嵌套范围”内,以便它们可以像x这里一样隐藏标识符。

@GeorgesDupéron在球拍用户列表的这封电子邮件中找到了解决此问题的方法,使用(make-syntax-introducer)onmod-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))]))])))
于 2016-06-25T19:15:46.563 回答