Racket 中的模块 meta-language

Module meta-language in Racket

我正在尝试在 Racket 中编写一个模块 meta-language mylang,它接受第二种语言并向其传递修改后的 body,这样:

(module foo mylang typed/racket body)

相当于:

(module foo typed/racket transformed-body)

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

我尝试了一个保持 body 不变的简单版本。它工作正常 on the command-line,但在 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)

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

你可以做的一件事,我认为这不违反你的要求,就是把它放在一个模块中,完全展开那个模块,然后在 #%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)

如果您想以某种方式转换 body,您可以在扩展之前或之后进行。

插入额外 (#%require lng) 的 pattern-matching 是必要的,因为在 lng 可用的上下文中扩展模块 body 是不够的。将 mod-body 代码从 module 形式中取回意味着绑定将引用 lng,但 lng 在 run-time 中不可用。这就是为什么没有它我得到 require: namespace mismatch; reference to a module that is not available 错误,这就是为什么它需要在扩展后添加。

根据评论更新

然而,正如@GeorgesDupéron 在评论中指出的那样,这引入了另一个问题。如果 lng 提供了一个标识符 x 并且使用它的模块导入了一个不同的 x,那么在不应该存在的地方就会出现导入冲突。相对于模块语言,要求行应该在 "nested scope" 中,这样它们就可以隐藏像 x 这样的标识符。

@GeorgesDupéron 在 email on the racket users list 中找到了这个问题的解决方案,在 mod-body 上使用 (make-syntax-introducer) 来生成嵌套范围。

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