我试图在Racket中编写一个模块元语言mylang
,它接受第二种语言传递修改后的正文,这样:
(module foo mylang typed/racket body)
相当于:
(module foo typed/racket transformed-body)
当然,typed/racket
部分可以用任何其他模块语言替换。
我尝试了一个让身体保持不变的简单版本。它工作正常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)
要求(即我避免使用的解决方案):
(require (only-in typed/racket))
模块中添加mylang
可以解决这个问题,但我对一般解决方案很感兴趣,mylang
无需了解typed/racket
在al(即如果某人添加了新语言foo
,那么mylang
应该开箱即用)。此外,我对声明子模块并立即require
并重新provide
它,as is done here的技巧不感兴趣,因为这会改变路径实际的模块(例如main
和test
放松了它们的特殊行为。
在编译时它也会变慢,因为子模块会多次visited和/或instantiated(这可以通过编写(begin-for-syntax (displayln 'here))
来看到,并且对大型文件有明显的影响typed/racket
个程序。
如果DrRacket中的箭头适用于委托语言提供的内置插件,则为奖励积分,例如:在上面的示例中,有ann
,+
和Number
到typed/racket/base
的箭头。
答案 0 :(得分: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
,那么就会出现导入冲突。要求行应该在模块语言的“嵌套范围”中,以便它们可以在此处为x
标记标识符。
@GeorgesDupéron在此email on the 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))]))])))