Найти элементы 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))))
Другие вопросы по тегам