Как читать каталог файлов, построчно, лениво в Clojure - PullRequest
0 голосов
/ 16 декабря 2018
(->> "/Users/micahsmith/printio/gooten-import-ai/jupyter/data"
     File.
     file-seq
     (filter #(-> ^File % .getAbsolutePath (str-contains? ".json")))
     (mapcat (fn [^File file]
            (with-open [ rdr (io/reader file)]
              (line-seq rdr)))))

Я пытаюсь читать каталог файлов json построчно, лениво, чтобы я мог лениво выполнить операцию с данными.

Я продолжаю получать java.io.IOException: Stream closed -- как я могу потреблять это, не закрывая читателя слишком рано?

Ответы [ 2 ]

0 голосов
/ 17 декабря 2018

Функция with-open предназначена для того, чтобы отговорить вас от этого, поскольку дескрипторы файлов и другие ресурсы операционной системы - это то, с чем вам следует обращаться осторожно, а не лениво.Вы должны выполнять всю обработку содержимого файла в динамической области действия вашего with-open.Таким образом, вместо возврата отложенной последовательности вы должны принять функцию в качестве аргумента и вызывать эту функцию в отложенной последовательности, пока она находится в области действия with-open.Эта функция, конечно, не должна возвращать другую ленивую последовательность, а вместо этого обрабатывать весь ввод перед возвратом.

Так что типичное использование для такой вещи выглядит так:

(defn process-file [filename process]
  (with-open [f (io/reader filename)]
    (process (line-seq f))))

Это немногоболее сложный, когда у вас есть список with-open последовательностей - вы не можете просто вызвать process один раз.Одна вещь, которую вы могли бы сделать, это вернуть список результатов вызова process для каждого файла:

(defn process-files [filenames process]
  (for [filename filenames]
    (with-open [f (io/reader filename)]
      (process (line-seq f)))))

Тогда, если вам нужно выполнить какую-то глобальную операцию над этим, вы можете reduce над результатомprocess-files.

0 голосов
/ 16 декабря 2018

Проблема в том, что with-open вызывает .close, когда программа выходит из области действия, к которой она относится, но все строки не обязательно были прочитаны к этому моменту.

Мое решение, вероятно, является оскорбительной мерзостью,никогда бы не увидел дневного света, но вот идея: создайте "lazy-seq", который просто вызывает .close, и объедините его в конец списка line-seq:

(defn lazy-lines [^File file]
  (let [rdr (io/reader file)]
    (lazy-cat (line-seq rdr)
              (do (.close rdr)
                  nil)))) ; Explicit nil to indicate termination

(defn get-lines [^String path]
  (->> path
       (File.)
       (file-seq)
       (filter #(-> ^File % (.getAbsolutePath) (clojure.string/includes? ".json")))
       (mapcat lazy-lines)))

Из моего быстрого тестирования с файлами на моем рабочем столе, похоже, работает.Если вы добавляете println в завершающий lazy-seq, он печатается, как и ожидалось, поэтому файл закрывается .

Я не решаюсь предложить это решение, так как оно полагаетсяна проведение побочных эффектов внутри ленивого списка, который я был вынужден «чувствовать неправильно» по понятным причинам.Основным недостатком этого метода является то, что файл не будет закрыт, пока не будет проанализирована вся последовательность, и файл будет оставаться открытым все время, пока не будет достигнут конец.Однако, учитывая ограничения, я не понимаю, как можно было бы избежать любой из этих проблем.


Я понял, что использовал lazy-cat немного неправильно.У меня была лишняя ненужная lazy-seq оболочка.Это сейчас исправлено.Вы также можете просто использовать что-то вроде

(apply concat (line-seq rdr)
              (lazy-seq (do (.close rdr)
                            nil))))))

вместо lazy-cat.

...