在 eval 中访问当前绑定的对象

Get access to currently bound objects in eval

警告

我知道我在这里要问的通常是邪恶的。我之所以这样问是因为我想进行测试以确保即使用户确实调用了 eval (这个问题是关于什么的)我的静态分析也能正常工作。要了解更多信息,请查看 On eval in dynamic languages generally and in Racket specifically

问题

在许多其他动态语言中,我可以使用eval 来修改我当前环境的状态。我想在 Racket 中做类似的事情。例如,我想写这样的东西:

#lang racket
(define x 5)
(eval '(set! x 6))
(displayln x)

并将 x 设置为 6。在 Racket 中可以做这样的事情吗?

这可以使用命名空间锚点。具体来说,你想要 define-namespace-anchor and namespace-anchor->namespace.

具体来说,您在希望绑定语法对象的代码中使用 define-namespace-anchor。然后您使用 namespace-anchor->namespace 将其转换为命名空间,可以将其参数化为 current-namespace,或者直接传递给 eval。

您的代码将如下所示:

#lang racket
(define-namespace-anchor foo)
(define x 5)
(eval '(set! x 6) (namespace-anchor->namespace foo))
(displayln x)

会给你以下错误:

. set!: assignment disallowed;
 cannot modify a constant
  constant: x

这实际上是因为编译器将 x 设置为静态变量,因为它认为它永远不会发生变异。 (因此可以在很多地方进行优化。)

您可以通过静态调用 set! 一次让编译器相信它会发生变异。

#lang racket
(define-namespace-anchor foo)
(define x (void))
(set! x 5)
(eval '(set! x 6) (namespace-anchor->namespace foo))
(displayln x)

这将打印出 6,这是您所期望的。

尽管请注意,这仅适用于 set! 之类的事情。这并不意味着 x 本身永远不会发生变异。例如,我们可以使用 boxunboxset-box!

#lang racket
(define-namespace-anchor foo)
(define x (box 5))
(eval '(set-box! x 6) (namespace-anchor->namespace foo))
(displayln (unbox x))

这也适用于任何可变数据结构,例如向量、可变列表或可变哈希表。