_评论者: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宏在这一点上与我意见一致,因为如果体(body)计算抛出异常,则结果对象不会被实现(根据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]。在函数调用过程中,有一个字段被设置为nil,这是一个很好的做法,为了让垃圾编译器收集对象,然而这应该只在函数成功完成后才进行。这可以改变吗?