在命名空间中,我定义了两个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 jar
或lein 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...
我认为这也是可以预料到的,因为我认为编译器不能处理未绑定的变量。
在任何情况下,如果要预期所有这些行为,为什么人们使用前向声明如果无法编译?我可能在这里遗漏了一些东西。
答案 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