_评论者: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](对LazySeq类似),在体fn调用成功返回后将其设置为null(表示实现)。
`:once`标志只影响[编译器的这部分|
https://github.com/clojure/clojure/blob/d0c380d9809fd242bec688c7134e900f0bbedcac/src/jvm/clojure/lang/Compiler.java#L4701]。在函数调用过程中,在该处将某个字段设置为nil,这确实是一个非常好的原因,因为这样可以让垃圾收集器收集对象,然而,这应该在函数成功完成后才能进行。这可以改变吗?