假设我有一个模块foo.rkt
在阶段1提供了x
。
#lang racket
(begin-for-syntax
(define x 5)
(provide x))
运行(module->exports "foo.rkt")
后,您将返回((1 (x ())))
,这意味着在阶段1提供了x
,并且没有提供其他绑定。
现在,在另一个模块中,我可以在运行时使用x
静态导入for-template
:
#lang racket
(require (for-template "foo.rkt"))
x ; => 5
但这是静态的,因此它将始终发生。
如果这是在阶段0,则可以使用dynamic-require
。但是似乎您只能使用dynamic-require
运行第1阶段代码,而不能从该运行代码中获取任何值。
还有dynamic-require-for-syntax
,但我永远无法工作。
最后,还有namespace-require
,但是它将它带到名称空间的阶段1中,而不是阶段0中。所以我可以做类似(eval '(begin-for-syntax (writeln x))
的操作,但是只会打印{ {1}},而不是返回它。
还有x
,但它似乎也只返回阶段0的值。
那么,无论如何,我是否可以从模块动态(而非静态)导入阶段1变量?
答案 0 :(得分:2)
是的,有办法,但是有点恶心。
首先,我们需要创建一个基本的名称空间,以便使用(define ns (make-base-namespace))
之类的技巧。
接下来,我实际上建议使用namespace-require/expansion-time
而不是namespace-require
。它将仅实例化模块(也仅运行阶段1代码)。
这样做,x
不会导入到名称空间中,而是在阶段1中,因此我们可以编写宏以通过3d语法从阶段1到阶段0“走私”它。
该宏将看起来像:
(eval '(define-syntax (cheater-x stx)
#`'#,(datum->syntax #f x)))
现在您可以执行(eval 'cheater-x)
来获取x
的值。
您的总体代码应如下所示:
(define (dynamic-require-from-syntax module binding)
(define ns (make-base-namespace))
(parameterize ([current-namespace ns])
(namespace-require 'racket)
(namespace-require/expansion-time module)
(eval `(define-syntax (cheater-x stx)
#`'#,(datum->syntax #f ,binding)))
(eval 'cheater-x)))
(dynamic-require-from-syntax "foo.rkt" 'x) ; => 5
很显然,您可以将此函数设置为在多个调用上使用相同的名称空间,这样它就不会在每次调用该模块时重新实例化该模块。但这是一个不同的答案。