假设我有一些模块foo.rkt
在第1阶段提供x
。
#lang racket
(begin-for-syntax
(define x 5)
(provide x))
当您运行(module->exports "foo.rkt")
时,您将返回((1 (x ())))
,这意味着x
在阶段1提供,并且不提供其他绑定。
现在,在另一个模块中,我可以使用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))
的事情,但这只会打印x
的值,而不是返回它。
还有namespace-variable-value
,但它似乎也只是在阶段0返回值。
那么,无论如何我可以动态(非静态地)从模块导入阶段1变量吗?
是的,有一种方法,但它有点恶心。
首先,我们需要创建一个基本命名空间,所以像(define ns (make-base-namespace))
这样的东西就可以了。
接下来,我实际上建议使用namespace-require/expansion-time
而不是namespace-require
。它只会实例化模块(也就是说只运行第1阶段代码)。
这样做,x
不会导入到命名空间,但是在阶段1,所以我们可以编写一个宏来将它从第1阶段“走私”到阶段0到3d语法。
宏将看起来像:
(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
显然,您可以设置此函数以在多个调用上使用相同的命名空间,这样每次调用它时都不会重新实例化该模块。但这是一个不同的答案。