Clojure 查找已实现的LazySeq元素

Clojure 查找已实现的LazySeq元素,clojure,lazy-sequences,Clojure,Lazy Sequences,我有一大堆在实现时创建的连接。如果在尝试创建连接时发生异常,我希望遍历LazySeq中已经实现的所有连接并关闭它们。比如: (try (dorun connections) (catch ConnectException (close-connections connections))) 但这并不完全有效,因为关闭连接将再次尝试实现连接。我只想关闭已经实现的连接,而不是实现其他连接。有什么想法吗?更新:虽然此答案适用于原始问题中提供的上下文(在序列上运行doall,并确定在出现异常

我有一大堆在实现时创建的连接。如果在尝试创建连接时发生异常,我希望遍历LazySeq中已经实现的所有连接并关闭它们。比如:

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

但这并不完全有效,因为
关闭连接
将再次尝试实现连接。我只想关闭已经实现的连接,而不是实现其他连接。有什么想法吗?

更新:虽然此答案适用于原始问题中提供的上下文(在序列上运行
doall
,并确定在出现异常时实现了哪些),但它包含一些缺陷,不适合问题标题建议的一般用途。然而,它确实提供了一个可能有助于理解的理论(但有缺陷)基础。如果你在理解这个答案时遇到困难,这个答案可能会帮助你把事情再细分一些。它还说明了您可能会遇到的几个陷阱。但除此之外,忽略这个答案

LazySeq
实现了
IPending
,因此理论上这应该像迭代连续的尾部序列一样简单,直到
实现为止?
返回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
没有实现
ipend
,对其中一个调用
realized?
将抛出异常。为了解决这个问题,我们可以使用
LazySeq
来显式构建一个
LazySeq
,对于任何序列都将保持
LazySeq

(defn lazify [s]
  (if (empty? s)
    nil
    (lazy-seq (cons (first s) (lazify (rest s))))))
Edit:正如MichałMarczyk在一篇评论中指出的那样,
lazify
并不保证底层序列被延迟使用。事实上,它可能会实现以前未实现的项(但似乎只是第一次抛出异常)。其唯一目的是保证调用
rest
会导致
nil
LazySeq
。换句话说,它运行得足够好,可以运行下面的示例,但是YMMV

现在,如果我们在
dorun
和cleanup代码中使用相同的“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))))
代码: 这将返回先前实现的输入序列初始片段作为向量:

(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
(使用
;=>
指示打印输出。)

讨论:
实现了吗?
确实是一条路,正如内森所建议的那样。然而,正如我在对Nathan答案的评论中所解释的,我们还必须确保不会无意中对输入调用
seq
,因为这会导致之前未实现的输入seq片段被实现。这意味着诸如
非空
空?
之类的函数将被取消,因为它们是按照
顺序
实现的

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


此外,虽然像
lazify
这样的函数对于不排序的序列很有用,但它们不会阻止其底层seq以分块的方式实现;相反,它们使处理层(
map
filter
等)能够以不确定的方式运行,即使原始输入序列被分块。事实上,在这种正在实现的“延迟”/“未锁定”的seq与其正在实现的底层(可能是分块的)seq之间根本没有任何联系。(事实上,在输入序列的其他观察者在场的情况下,没有办法建立这样的连接;如果没有其他观察者,这是可以实现的,但代价是使
lazify
编写起来更加乏味。)

这不起作用,因为(1)
notempty
empty?
对其参数调用
seq
,从而实现之前未实现的惰性seq部分;(2)
lazify
返回一个seq,该seq将一次实现一个元素,但其底层seq当然将以其通常的方式实现,因此“lazify”seq只能实现一个链接,即使底层分块seq实现了完整的初始分块。请尝试
(defn lazy printer[n](lazy seq(如果不是(零?n)(println n)(cons n(lazy printer)(dec n‘‘‘‘‘)’)
(take realized(lazy printer 10))
。这应该什么也不打印,但实际上打印的是10到1之间的整数。@Michału Marczyk谢谢你提供的信息。我怀疑lazyfy并没有想象的那么懒(毕竟它确实调用了
empty?
,这必须检查是否有第一个,对吗?)。知道我的怀疑是正确的是好的(但很烦人;-)。然而,在这种情况下,
lazify
的主要目的是确保调用
rest
总是返回
LazySeq
(在正常情况下,该顺序将始终完全实现)。重要的是,如果实现其中一项