_由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}时,这种情况不会发生。现在有人可能会争辩说上面的函数被调用了两次,这正是:once标志不应该做的事情,但我争辩说失败的调用不应计入:once标志的调用次数。延迟和lazy-seq宏也同意我的观点,因为如果评估体抛出异常,则产生的对象不会被实现(如realized?函数所示),因此,在延迟/lazy-seq的重新评估中,将重复评估/实现评估体。
尝试在上述代码的第一次评估之后使用(实现?延迟)。在实现中,例如可以通过[这里查看 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,这是为了让垃圾收集器收集对象的好理由,然而这应该在函数成功完成后才做。这可以改变吗?