_Comment made by: hank_
这导致了一些可疑的结果,如下所示:以下表达式在上一次评估时会导致异常,在后续评估时会导致“w00t!”。
(def delayed
(let [a true]
(delay
(if a
 ;(throw (Exception.)))
"w00t!"))))
这样尝试一下
(try
(print "delayed 1:" )
(println (force delayed))
(catch Exception ex (println ex)))
(try
(print "delayed 2:")
(println (force delayed))
(catch Exception ex (println ex)))
结果是
delayed 1:#<Exception java.lang.Exception>
delayed 2:w00t!
此代码显示问题与所怀疑的 :once 标志有关。
(def test-once
(let [a true]
(^{:once true} fn* foo []
(println "a=" a)
(throw (Exception.)))))
调用该函数两次将显示 'a' 从 'true' 变为 'nil',可以这样尝试
(try
(print "test-once 1:")
(test-once)
(catch Exception ex (println ex)))
(try
(print "test-once 2:")
(test-once)
(catch Exception ex (println ex)))
结果是
test-once 1:a= true
#<Exception java.lang.Exception>
test-once 2:a= nil
#<Exception java.lang.Exception>
当移除^{:once true}后,这种情况就不会发生。现在有人可能会争论上面提到的 fn 被调用了两次,这正是装饰了 :once 标志时不应做的,但我认为调用失败不会算作对 :once 标志的调用。延迟和 lazy-seq 宏都同意这一点,因为如果体内部的评估抛出异常,则结果对象被认为尚未实现(根据 realized? 函数),因此延迟/lazy-seq 在重新评估时会再次尝试实现/评估体。
在上面的代码中第一次评估之后,尝试使用 (realized? delayed)。在实现中,例如对于 clojure.lang.Delay,可以在 [这里](https://github.com/clojure/clojure/blob/d0c380d9809fd242bec688c7134e900f0bbedcac/src/jvm/clojure/lang/Delay.java#L33)(类似的对于 LazySeq),调用成功后,body-fn 被设置为 null(意味着已实现)。
:once 标志仅影响 [编译器这一部分|
https://github.com/clojure/clojure/blob/d0c380d9809fd242bec688c7134e900f0bbedcac/src/jvm/clojure/lang/Compiler.java#L4701]。在函数调用过程中,某些字段被设置为 nil,这完全合理,因为它可以让垃圾收集器回收对象,但这种操作只应在函数成功完成后进行。这可以改变吗?