评论来自: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.)))))
调用该 fn 两次将显示 '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]。在函数调用过程中,某个字段被设置为null,这是让垃圾回收器收集对象的好理由,然而这只应在函数成功完成后才这么做。这能改变吗?