Найти элементы LazySeq, которые были реализованы
У меня есть LazySeq соединений, которые создаются при реализации. Если при попытке создать соединение возникает исключение, я бы хотел перебрать все соединения, которые уже были реализованы в LazySeq, и закрыть их. Что-то вроде:
(try
(dorun connections)
(catch ConnectException (close-connections connections)))
Это не совсем работает, хотя, так как close-connections
будет пытаться реализовать соединения снова. Я только хочу закрыть связи, которые были реализованы, а не реализовать дополнительные соединения. Есть идеи для этого?
2 ответа
Код:
Это возвращает ранее реализованный начальный фрагмент входной последовательности как вектор:
(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?
это действительно путь, как предполагает Натан. Однако, как я объяснил в моих комментариях к ответу Натана, нужно также убедиться, что он случайно не позвонил seq
на входе, так как это может привести к реализации ранее нереализованных фрагментов входного seq. Это означает, что такие функции, как non-empty
а также empty?
вне, так как они реализованы с точки зрения seq
,
(На самом деле, невозможно понять, является ли ленивый seq пустым, не осознавая этого.)
Кроме того, в то время как функции, такие как lazify
полезны для расцепления последовательностей, они не препятствуют реализации их базовых последовательностей порциями; скорее они позволяют слои обработки (map
, filter
и т. д.) работать без чанков, даже если их исходные входные последовательности чанкованы. На самом деле нет никакой связи между тем, как реализуется такой "lazified" / "unchunked" seq, и реализуется его основной, возможно chunked seq. (На самом деле нет никакой возможности установить такую связь в присутствии других наблюдателей входной последовательности; в отсутствие других наблюдателей это может быть достигнуто, но только за счет lazify
значительно более утомительно писать.)
Обновление: пока этот ответ будет работать для контекста, представленного в исходном вопросе (выполняется doall
над последовательностью и определите, какие из них были реализованы, если было исключение), она содержит несколько недостатков и не подходит для общего использования, предложенного заголовком вопроса. Это, однако, представляет теоретическую (но ошибочную) основу, которая может помочь в понимании ответа Михаила Марчика. Если у вас возникли проблемы с пониманием этого ответа, этот ответ может помочь, если немного разбить вещи. Это также иллюстрирует несколько ловушек, с которыми вы можете столкнуться. Но в противном случае просто проигнорируйте этот ответ.
LazySeq
инвентарь IPending
, так что теоретически это должно быть так же просто, как итерация последовательных последовательностей хвостов до realized?
возвращает ложь:
(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
это было реализовано.
Редактировать: Хорошо, не совсем. Это будет работать для определения того, какие предметы были реализованы до того, как было сгенерировано исключение. Однако, как указывает Михал Марцизк, это приведет к тому, что каждый элемент в последовательности будет реализован в других контекстах.
Затем вы можете написать свою логику очистки следующим образом:
(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))))))
Редактировать: Как отметил Михал Марчик в комментарии, lazify
не гарантирует, что основная последовательность лениво потребляется. На самом деле, он, вероятно, будет реализовывать ранее нереализованные предметы (но, похоже, выдает исключение только в первый раз). Его единственная цель - гарантировать, что вызов rest
приводит либо nil
или LazySeq
, Другими словами, это работает достаточно хорошо, чтобы запустить приведенный ниже пример, но YMMV.
Теперь, если мы используем одну и ту же последовательность "lazified" в обоих 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))))