Update: While this answer will work for the context presented in the original question (running doall
over a sequence, and determine which ones were realize if there was an exception), it contains several flaws and is unsuitable for the general use suggested by the question title. It does, however, present a theoretical (but flawed) basis that might help in understanding Michał Marczyk's answer. If you are having trouble understanding that answer, this answer might help by breaking things down a little more. It also illustrates several pitfalls you might encounter. But otherwise, just ignore this answer.
LazySeq
implements IPending
, so theoretically this should be as easy as iterating over successive tail sequences until realized?
returns false:
(defn successive-tails [s]
(take-while not-empty
(iterate rest s)))
(defn take-realized [s]
(map first
(take-while realized?
(successive-tails s))))
Now, if you truly have a 100% LazySeq
from start to finish, that's it -- take-realized
will return the items of s
that have been realized.
Edit: Ok, not really. This will work for determining which items were realized before an exception was thrown. However, as Michal Marcyzk points out, it will cause every item in the sequence to be realized in other contexts.
You can then write your cleanup logic like this:
(try
(dorun connections) ; or doall
(catch ConnectException (close-connections (take-realized connections))))
However, be aware that a lot of Clojure's "lazy" constructs are not 100% lazy. For example, range
will return a LazySeq
, but if you start rest
ing down it, it turns into a ChunkedCons
. Unfortunately, ChunkedCons
does not implement IPending
, and calling realized?
on one will throw an exception. To work around this, we can use lazy-seq
to explicitly build a LazySeq
that will stay a LazySeq
for any sequence:
(defn lazify [s]
(if (empty? s)
nil
(lazy-seq (cons (first s) (lazify (rest s))))))
Edit: As Michał Marczyk pointed out in a comment, lazify
does not guarantee the underlying sequence is lazily consumed. In fact, it will probably realize previously unrealized items (but appears to only throw an exception the first time through). Its sole purpose is to guarantee that calling rest
results in either nil
or a LazySeq
. In other words, it works well enough to run the example below, but YMMV.
Now if we use the same "lazified" sequence in both the dorun
and the cleanup code, we will be able to use take-realize
. Here's an example that illustrates how to build an expression that will return a partial sequence (the part before the failure) if an exception occurs while realizing it:
(let [v (for [i (lazify (range 100))]
(if (= i 10)
(throw (new RuntimeException "Boo!"))
i))]
(try
(doall v)
(catch Exception _ (take-realized v))))