我有一个在实现时创建的LazySeq连接。如果在尝试创建连接时发生异常,我想迭代已经在LazySeq中实现的所有连接并关闭它们。类似的东西:
(try
(dorun connections)
(catch ConnectException (close-connections connections)))
虽然close-connections
将尝试再次实现连接,但这并不是很有效。我只想关闭已实现的连接,而不是实现其他连接。这样做有什么想法吗?
答案 0 :(得分:5)
这将先前实现的输入seq的初始片段作为向量返回:
(defn take-realized [xs]
(letfn [(lazy-seq? [xs]
(instance? clojure.lang.LazySeq xs))]
(loop [xs xs
out []]
(if (or (and (lazy-seq? xs) (not (realized? xs)))
(and (not (lazy-seq? xs)) (empty? xs)))
out
(recur (rest xs) (conj out (first xs)))))))
在REPL进行测试:
(defn lazy-printer [n]
(lazy-seq
(when-not (zero? n)
(println n)
(cons n (lazy-printer (dec n))))))
(take-realized (lazy-printer 10))
;= []
(take-realized (let [xs (lazy-printer 10)] (dorun (take 1 xs)) xs))
;=> 10
;= [10]
;; range returns a lazy seq...
(take-realized (range 20))
;= []
;; ...wrapping a chunked seq
(take-realized (seq (range 40)))
;= [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
; 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31]
;; NB. *each* chunk of range gets its own LazySeq wrapper,
;; so that it is possible to work with infinite (or simply huge) ranges
(使用;=>
表示打印输出。)
realized?
确实是要走的路。但是,正如我在对Nathan的答案的评论中所解释的那样,还必须确保一个人不会无意中在一个人的输入上调用seq
,因为这会导致输入序列的先前未实现的片段被实现。这意味着non-empty
和empty?
等功能已经用完,因为它们是以seq
实现的。
(事实上,根本不可能在没有意识到的情况下判断懒惰的seq是否为空。)
此外,虽然像lazify
这样的函数对非拆分序列很有用,但它们并不能阻止它们的底层序列以分块的方式实现;相反,它们使得处理层(map
,filter
等)能够以未分块的方式运行,即使它们的原始输入序列是分块的。实际上,在实现这样的“lazified”/“unchunked”seq与其底层的,可能是chunked seq之间实际上没有任何联系。 (事实上,没有办法在输入seq的其他观察者面前建立这样的连接;没有其他观察者,它可以完成,但只是以使lazify
编写相当繁琐为代价。 )
答案 1 :(得分:3)
更新:虽然此答案适用于原始问题中显示的上下文(在序列上运行doall
,并确定在发生异常时已实现哪些内容),包含几个缺陷,不适合问题标题建议的一般用途。然而,它确实提供了理论(但有缺陷)的基础,可能有助于理解Michał Marczyk's answer。如果您在理解答案时遇到困难,那么这个答案可能会有所帮助。它还说明了您可能遇到的几个陷阱。但除此之外,请忽略这个答案。
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))))
现在,如果您从头到尾确实拥有100%LazySeq
,那就是 - take-realized
将返回已经实现的s
项。
编辑:好的,不是真的。这将用于确定在抛出异常之前实现了哪些项目。然而,正如Michal Marcyzk指出的那样,它将导致序列中的每个项目在其他环境中实现。
然后您可以像这样编写清理逻辑:
(try
(dorun connections) ; or doall
(catch ConnectException (close-connections (take-realized connections))))
然而,要注意很多Clojure的“懒惰”结构并非100%懒惰。例如,range
将返回LazySeq
,但如果您开始rest
,则会变为ChunkedCons
。遗憾的是,ChunkedCons
未实现IPending
,并且在其中调用realized?
会引发异常。要解决此问题,我们可以使用lazy-seq
显式构建LazySeq
LazySeq
任何序列:
(defn lazify [s]
(if (empty? s)
nil
(lazy-seq (cons (first s) (lazify (rest s))))))
编辑正如MichałMarczyk在评论中指出的那样,lazify
确实不保证基础序列被懒散消耗。事实上,它可能会实现以前未实现的项目(但似乎只是第一次抛出异常)。其唯一目的是保证调用rest
导致nil
或LazySeq
。换句话说,它运行良好,足以运行下面的示例,但YMMV。
现在,如果我们在dorun
和清理代码中使用相同的“lazified”序列,我们将能够使用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))))