Блоки итератора в Clojure?

Я использую clojure.contrib.sql выбирать некоторые записи от базы данных SQLite.

(defn read-all-foo []
  (with-connection *db*
    (with-query-results res ["select * from foo"]
       (into [] res))))

Теперь, я действительно не хочу понимать целую последовательность прежде, чем возвратиться из функции (т.е. Я хочу сохранить ее ленивой), но если я возвращаюсь res непосредственно или перенесите его некоторая ленивая обертка (например, я хочу сделать определенное map преобразование на последовательности результата), связанная с SQL привязка будет сброшена, и соединение будет закрыто после того, как я возвращусь, так понимание, что последовательность выдаст исключение.

Как я могу включить целую функцию в закрытие и возвратить своего рода блок итератора (как yield в C# или Python)?

Или есть ли другой способ возвратить ленивую последовательность из этой функции?

6
задан Alex B 3 May 2010 в 06:53
поделиться

3 ответа

resultset-seq , который возвращает with-query-results , вероятно, уже такой же ленивый, как и вы собираюсь получить. Как вы сказали, лень работает только до тех пор, пока открыта ручка. Нет никакого способа обойти это. Вы не можете читать из базы данных, если дескриптор базы данных закрыт.

Если вам нужно выполнить ввод-вывод и сохранить данные после закрытия дескриптора, откройте дескриптор, быстро проглотите его (побеждая лень), закройте дескриптор и после этого работайте с результатами. Если вы хотите перебрать некоторые данные, не сохраняя все сразу в памяти, откройте дескриптор, получите ленивую последовательность данных, доза над ним, затем закройте дескриптор.

Итак, если вы хотите что-то сделать с каждой строкой (для побочных эффектов) и отбросить результаты, не поглощая весь набор результатов в памяти, то вы можете сделать следующее:

(defn do-something-with-all-foo [f]
  (let [sql "select * from foo"]
    (with-connection *db*
      (with-query-results res [sql]
        (doseq [row res]
          (f row))))))

user> (do-something-with-all-foo println)
{:id 1}
{:id 2}
{:id 3}
nil

;; transforming the data as you go
user> (do-something-with-all-foo #(println (assoc % :bar :baz)))
{:id 1, :bar :baz}
{:id 2, :bar :baz}
{:id 3, :bar :baz}

Если вы хотите, чтобы ваши данные оставались в памяти долго - term, то вы можете все это пропустить, используя указанную выше функцию read-all-foo (таким образом, победив лень). Если вы хотите преобразовать данные, то сопоставьте с результатами после того, как вы их все извлекли. В этот момент все ваши данные будут в памяти, но карта вызовет себя, и ваши преобразования данных после выборки будут ленивыми.

7
ответ дан 10 December 2019 в 00:35
поделиться

Я никогда раньше не использовал SQLite с Clojure, но я предполагаю, что with-connection закрывает соединение, когда его тело было оценено. Поэтому вам нужно управлять соединением самостоятельно, если вы хотите держать его открытым, и закрывать его, когда вы закончите чтение интересующих вас элементов.

0
ответ дан 10 December 2019 в 00:35
поделиться

На самом деле можно добавить "завершающий побочный эффект" в ленивую последовательность, который будет выполнен один раз, когда вся последовательность будет использована в первый раз:

(def s (lazy-cat (range 10) (do (println :foo) nil)))

(first s)
; => returns 0, prints out nothing

(doall (take 10 s))
; => returns (0 1 2 3 4 5 6 7 8 9), prints nothing

(last s)
; => returns 9, prints :foo

(doall s)
; => returns (0 1 2 3 4 5 6 7 8 9), prints :foo
; or rather, prints :foo if it it's the first time s has been
; consumed in full; you'll have to redefine it if you called
; (last s) earlier

Я не уверен, что использовал бы это для закрытия соединения с БД, хотя - я думаю, что это считается лучшей практикой не держать соединение с БД бесконечно, и размещение вашего вызова закрытия соединения в конце вашей ленивой последовательности результатов не только удержит соединение дольше, чем это строго необходимо, но и откроет возможность того, что ваша программа потерпит неудачу по несвязанной причине без закрытия соединения. Таким образом, для этого сценария я бы обычно просто сбрасывал все данные. Как говорит Брайан, вы можете хранить их в необработанном виде, а затем лениво выполнять любые преобразования, так что все будет в порядке, если вы не пытаетесь получить действительно огромный набор данных одним куском.

Но тогда я не знаю ваших точных обстоятельств, поэтому, если это имеет смысл с вашей точки зрения, вы определенно можете вызвать функцию закрытия соединения в хвостовой части вашей последовательности результатов. Как отмечает Michiel Borkent, вы не сможете использовать with-connection, если хотите сделать это.

3
ответ дан 10 December 2019 в 00:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: