_Comment made by: hank_
The above leads to dodgy outcomes such as: The following expression leads to an Exception on 1st evaluation and to "w00t!" on subsequent ones.
(def delayed
(let [a true]
(delay
(if a
(throw (Exception.))
"w00t!"))))
Try it like this:
(try
(print "delayed 1:" )
(println (force delayed))
(catch Exception ex (println ex)))
(try
(print "delayed 2:")
(println (force delayed))
(catch Exception ex (println ex)))
Results in:
delayed 1:#<Exception java.lang.Exception>
delayed 2:w00t!
This code shows that the problem is tied to the :once flag as suspected.
(def test-once
(let [a true]
(^{:once true} fn* foo []
(println "a=" a)
(throw (Exception.)))))
Invoking the fn twice will show 'a' turning from 'true' to 'nil', try it like this:
(try
(print "test-once 1:")
(test-once)
(catch Exception ex (println ex)))
(try
(print "test-once 2:")
(test-once)
(catch Exception ex (println ex)))
Results in:
test-once 1:a= true
#<Exception java.lang.Exception>
test-once 2:a= nil
#<Exception java.lang.Exception>
That doesn't happen when the ^{:once true} is removed. Now one could argue that above fn is invoked twice, which is exactly what one is not supposed to do when decorated with the :once flag, but I argue that an unsuccessful call doesn't count as invocation towards the :once flag. The delay and lazy-seq macros agree with me there as the resulting objects are not considered realized (as per realized? function) if the evaluation of the body throws an exception, and realization/evaluation of the body is therefore repeated on re-evaluation of the delay/lazy-seq.
Try this using (realized? delayed) after the first evaluation in the code above. In the implementation this can be seen e.g. [here for clojure.lang.Delay|
https://github.com/clojure/clojure/blob/d0c380d9809fd242bec688c7134e900f0bbedcac/src/jvm/clojure/lang/Delay.java#L33] (similarly for LazySeq), the body-fn is set to null (meaning realized) after the invocation returns *successfully* only.
The :once flag affects [this part of the compiler only|
https://github.com/clojure/clojure/blob/d0c380d9809fd242bec688c7134e900f0bbedcac/src/jvm/clojure/lang/Compiler.java#L4701]. Some field is set to nil there in the course of a function invocation, for the good reason of letting the garbage compiler collect objects, however this should to be done after the function successfully completes only. Can this be changed?