Использование содержимого файла с помощью Clojure core.async

Я пытаюсь использовать библиотеку Clojure core.async для использования/обработки строк из файла. Когда мой код выполняется, IOException: Stream closed является броском. Ниже приведен сеанс REPL, который воспроизводит ту же проблему, что и в моем коде:

(require '[clojure.core.async :as async])
(require '[clojure.java.io :as io])

; my real code is a bit more involved with calls to drop, map, filter
; following line-seq
(def lines
  (with-open [reader (io/reader "my-file.txt")]
    (line-seq reader)))

(def ch
  (let [c (async/chan)]
    (async/go
      (doseq [ln lines]
        (async/>! c ln))
      (async/close! c))
    c))

; line that causes the error
; java.io.IOException: Stream closed
(async/<!! ch)

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

Спасибо!


person Matheus Moreira    schedule 13.07.2016    source источник


Ответы (2)


Ваша проблема в операторе with-open. Файл закрывается, как только эта область выходит. Итак, вы открываете line-seq, а затем закрываете файл, прежде чем читать какие-либо строки.

Для большинства файлов вам будет лучше использовать функцию slurp:

(require '[clojure.string :as str])

(def file-as-str   (slurp "my-file.txt"))
(def lines         (str/split-lines file-as-str))

Видеть:

http://clojuredocs.org/clojure.core/slurp

http://clojuredocs.org/clojure.string/split-lines

person Alan Thompson    schedule 13.07.2016
comment
Понятно... Я хотел избежать одновременной загрузки всего содержимого файла в память, потому что он может быть большим. Вот почему я подумал об использовании комбинации with-open/line-seq. Учитывая это ограничение (размер файла), могу ли я что-то сделать? - person Matheus Moreira; 13.07.2016
comment
Кажется, что clojure нужен способ связать открытый ресурс с ленивой последовательностью, чтобы он не был закрыт до тех пор, пока ленивая последовательность не будет использована. Я также пытаюсь выполнять построчную обработку очень больших файлов и сталкиваюсь с этим, не помещая всю обработку в with-open - person theferrit32; 02.07.2020
comment
Вы можете изучить использование lazy-gen для имитации функции генератора: cljdoc.org/d/tupelo/tupelo/0.9.214/api/tupelo.core#lazy-gen См. этот вопрос: stackoverflow.com/questions/46434966/ - person Alan Thompson; 02.07.2020

Как указал @Alan, ваше определение lines закрывает файл без чтения всех его строк, потому что line-seq возвращает ленивую последовательность. Если вы расширите использование макроса with-open...

(macroexpand-1
 '(with-open [reader (io/reader "my-file.txt")]
    (line-seq reader)))

... вы получаете это:

(clojure.core/let [reader (io/reader "my-file.txt")]
  (try
    (clojure.core/with-open []
      (line-seq reader))
    (finally
      (. reader clojure.core/close))))

Вы можете решить эту проблему, закрыв файл после того, как закончите чтение из него, а не сразу:

(def ch
  (let [c (async/chan)]
    (async/go
      (with-open [reader (io/reader "my-file.txt")]
        (doseq [ln (line-seq reader)]
          (async/>! c ln)))
      (async/close! c))
    c))
person Sam Estep    schedule 13.07.2016
comment
Так что это действительно было мое недоразумение... Я думал, что with-open как-то удержит открытый ридер, пока он не иссякнет. Я приму ответ Алана, так как он первым указал на проблему, а ваш основан на его. :-) Но большое спасибо за дальнейшее разъяснение. - person Matheus Moreira; 13.07.2016