4

I have a LazySeq of connections that are created when realized. If an exception occurs while attempting to create a connection, I'd like to iterate through all of the connections that have already been realized in the LazySeq and close them. Something like:

(try  
  (dorun connections)
  (catch ConnectException (close-connections connections)))

This doesn't quite work though since close-connections will attempt to realize the connections again. I only want to close connections that have been realized, not realize additional connections. Any ideas for doing this?

4

2 回答 2

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自己的输入,因为这会导致输入 seq 的先前未实现的片段被实现。这意味着诸如non-empty和之类的功能empty?已经退出,因为它们是根据seq.

(事实上​​,根本不可能在没有意识到的情况下判断惰性序列是否为空。)

此外,虽然 like 函数lazify对取消分块序列很有用,但它们不会阻止其底层序列以分块方式实现;相反,它们使处理层(mapfilter)能够以非分块方式运行,即使它们的原始输入序列是分块的。实际上,在实现这种“惰性”/“未分块”的 seq 与其底层的、可能是分块的 seq 实现之间根本没有任何联系。(事实上​​,在输入 seq 的其他观察者在场的情况下,没有办法建立这样的连接;在没有其他观察者的情况下,它可以实现,但代价是lazify编写起来相当乏味。)

于 2013-12-03T14:30:07.563 回答
3

更新: 虽然此答案适用于原始问题中呈现的上下文(运行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))))

但是,请注意,许多 Clo​​jure 的“惰性”构造并不是 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.restnilLazySeq

现在,如果我们在 和清理代码中使用相同的“惰性”序列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))))
于 2013-12-03T07:53:05.137 回答