_由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](similarly for LazySeq),仅在invocation返回*成功*后才将body-fn设置为null(意味着realized)来实现(对LazySeq亦然)。
:once标志仅影响[编译器这部分|
https://github.com/clojure/clojure/blob/d0c380d9809fd242bec688c7134e900f0bbedcac/src/jvm/clojure/lang/Compiler.java#L4701]。在函数调用过程中,某个字段被设置为nil,理由是让垃圾编译器收集对象,然而这应该在函数成功完成后才能做。这能改变吗?