_评论者: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,原因是为了让垃圾编译器收集对象,然而这应该在函数成功完成后才做。这可以更改吗?