更新: 虽然此答案适用于原始问题中呈现的上下文(运行doall
序列,并确定是否存在异常),但它包含几个缺陷,不适合问题标题建议的一般用途. 然而,它确实提供了一个理论(但有缺陷的)基础,可能有助于理解Michał Marczyk 的答案。如果您在理解该答案时遇到困难,则此答案可能会通过将事情进一步分解来提供帮助。它还说明了您可能遇到的几个陷阱。但除此之外,请忽略这个答案。
LazySeq
实现IPending
,所以理论上这应该像迭代连续的尾序列一样简单,直到realized?
返回 false:
(defn successive-tails [s]
(take-while not-empty
(iterate rest s)))
(defn take-realized [s]
(map first
(take-while realized?
(successive-tails s))))
现在,如果你真的有一个LazySeq
从头到尾的100%,就是这样——take-realized
将返回s
已经实现的项目。
编辑:好的,不是真的。这将用于确定在引发异常之前实现了哪些项目。然而,正如 Michal Marcyzk 所指出的,它会导致序列中的每个项目都在其他上下文中实现。
然后,您可以像这样编写清理逻辑:
(try
(dorun connections) ; or doall
(catch ConnectException (close-connections (take-realized connections))))
但是,请注意,许多 Clojure 的“惰性”构造并不是 100% 惰性的。例如,range
将返回 a LazySeq
,但如果你开始rest
向下它,它会变成 a ChunkedCons
。不幸的是,ChunkedCons
没有实现IPending
,并且调用realized?
一个会抛出异常。为了解决这个问题,我们可以使用lazy-seq
显式构建一个对于任何序列LazySeq
都将保持a 的 a :LazySeq
(defn lazify [s]
(if (empty? s)
nil
(lazy-seq (cons (first s) (lazify (rest s))))))
编辑: 正如 Michał Marczyk 在评论中指出的那样,不lazify
保证底层序列被懒惰地消耗。事实上,它可能会实现以前未实现的项目(但似乎只在第一次通过时抛出异常)。它的唯一目的是保证调用的结果是or 或 a 。换句话说,它运行得很好,可以运行下面的示例,但是 YMMV.rest
nil
LazySeq
现在,如果我们在 和清理代码中使用相同的“惰性”序列dorun
,我们将能够使用take-realize
. 这是一个示例,说明如何构建一个表达式,如果在实现时发生异常,该表达式将返回部分序列(失败之前的部分):
(let [v (for [i (lazify (range 100))]
(if (= i 10)
(throw (new RuntimeException "Boo!"))
i))]
(try
(doall v)
(catch Exception _ (take-realized v))))