— 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.)))))
连续调用 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),体部fn在调用返回成功后才设置为null(表示实现)。
:once标志只影响[编译器的这部分|
https://github.com/clojure/clojure/blob/d0c380d9809fd242bec688c7134e900f0bbedcac/src/jvm/clojure/lang/Compiler.java#L4701]。在这个过程中,某个字段在函数调用的过程中设置为null,这是为了一个好理由,即让垃圾收集器回收对象,然而这只应该在函数成功完成之后进行。这能被更改吗?