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)
要求(即我宁愿避免的解决方案):
- 在
mylang
模块中添加一个 (require (only-in typed/racket))
使这项工作可行,但我对通用解决方案感兴趣,其中 mylang
不需要了解 typed/racket
at al(即如果 somebody 添加了一种新语言 foo
,那么 mylang
应该开箱即用)。
此外,我对声明子模块并立即 require
并重新 provide
它 as is done here 的技巧不感兴趣,因为这会改变实际模块的路径(例如 main
和 test
失去它们的特殊行为)。
它在 compile-time 时也更慢,因为子模块得到 visited and/or instantiated 次(这可以通过写 (begin-for-syntax (displayln 'here))
看出,并且对大 typed/racket
程序。
如果 DrRacket 中的箭头适用于 delegated-to 语言提供的 built-ins,则加分,例如在上面的示例中,箭头从 ann
、+
和 Number
到 typed/racket/base
。
你可以做的一件事,我认为这不违反你的要求,就是把它放在一个模块中,完全展开那个模块,然后在 #%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))]))])))
我正在尝试在 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)
要求(即我宁愿避免的解决方案):
- 在
mylang
模块中添加一个(require (only-in typed/racket))
使这项工作可行,但我对通用解决方案感兴趣,其中mylang
不需要了解typed/racket
at al(即如果 somebody 添加了一种新语言foo
,那么mylang
应该开箱即用)。 此外,我对声明子模块并立即
require
并重新provide
它 as is done here 的技巧不感兴趣,因为这会改变实际模块的路径(例如main
和test
失去它们的特殊行为)。它在 compile-time 时也更慢,因为子模块得到 visited and/or instantiated 次(这可以通过写
(begin-for-syntax (displayln 'here))
看出,并且对大typed/racket
程序。如果 DrRacket 中的箭头适用于 delegated-to 语言提供的 built-ins,则加分,例如在上面的示例中,箭头从
ann
、+
和Number
到typed/racket/base
。
你可以做的一件事,我认为这不违反你的要求,就是把它放在一个模块中,完全展开那个模块,然后在 #%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))]))])))