Clojure:交叉引用的变量,声明和编译错误

时间:2014-06-11 15:48:44

标签: java clojure

在命名空间中,我定义了两个vars(以及其他)是map:

(declare bar)

(def foo {:is-related-to bar})

(def bar {:is-related-to foo})

由于我定义bar时不存在foo,因此我使用(declare bar)向前声明它。

到目前为止没有任何问题,一切都在REPL中按预期工作。

我们唯一注意到的是,当我在REPL中检查foo时,我发现bar未绑定,我认为这是declare的使用预期:

#<Unbound Unbound: #'user/bar>

当我尝试使用lein jarlein ring war编译软件时出现问题(因为它是一个Ring应用程序)。我从编译器得到的错误是:

Exception in thread "main" java.lang.RuntimeException: Can't embed object in code, maybe print-dup not defined: Unbound: #'user/bar, compiling...

我认为这也是可以预料到的,因为我认为编译器不能处理未绑定的变量。

在任何情况下,如果要预期所有这些行为,为什么人们使用前向声明如果无法编译?我可能在这里遗漏了一些东西。

2 个答案:

答案 0 :(得分:6)

您不能以这种方式构造循环引用。它不起作用的原因是因为def计算作为绑定传递的表单,并且评估解析为Var的符号获取该Var的当前绑定。换句话说,放入地图foo的内容不是var bar引用,它是bar。在事实之后重新定义bar并不会影响foo的价值 - 这是Clojure中不可变性的重点。

前向声明通常用于允许函数之间的循环依赖。以下是有效的,因为在实际调用函数之前不会对函数体进行求值;当定义函数时,它确实是对已编译的Var的引用。

 (declare bar)

 (defn foo [x y]
   (bar x (* 2 y)))

 (defn bar
   ([x] (foo x 3))
   ([x y] (+ x y)))

答案 1 :(得分:2)

如果您想参考符号栏,您应该使用#'栏。当你使用bar时,你还没有定义bar的值。如果你使用#'栏,你可以参考你想要评估的栏符号,并且将被正确定义......

(declare bar)

(def foo {:is-related-to #'bar})

(def bar {:is-related-to #'foo})

user=> foo
{:is-related-to #'user/bar}
user=> (:is-related-to foo)
#'user/bar