В Clojure, lazy seqs всегда chunked?

У меня создалось впечатление, что ленивые секции всегда были помечены.

=> (take 1 (map #(do (print \.) %) (range)))
(................................0)

Как и ожидалось, напечатано 32 точки, потому что ленивый seq, возвращаемый range, разбивается на 32 элементарных фрагмента. Однако, когда вместо range я пытаюсь выполнить эту функцию со своей собственной функцией get-rss-feeds, lazy seq больше не кланяется:

=> (take 1 (map #(do (print \.) %) (get-rss-feeds r)))
(."http://wholehealthsource.blogspot.com/feeds/posts/default")

Печатается только одна точка, поэтому я думаю, что lazy-seq, возвращаемый get-rss-feeds, не разбивается на разделы. Действительно:

=> (chunked-seq? (seq (range)))
true

=> (chunked-seq? (seq (get-rss-feeds r)))
false

Вот источник для get-rss-feeds:

(defn get-rss-feeds
  "returns a lazy seq of urls of all feeds; takes an html-resource from the enlive library"
  [hr]
  (map #(:href (:attrs %))
       (filter #(rss-feed? (:type (:attrs %))) (html/select hr [:link])))

Таким образом, кажется, что chunkiness зависит от того, как создается ленивый seq. Я заглянул в источник для функции range, и есть намеки на то, что она выполняется "коротко". Поэтому я немного смущен относительно того, как это работает. Может кто-то прояснить?


Вот почему мне нужно знать.

Мне нужен следующий код: (get-rss-entry (get-rss-feeds h-res) url)

Вызов get-rss-feeds возвращает ленивую последовательность URL-адресов фидов, которые мне нужно изучить.

Вызов get-rss-entry ищет определенную запись (чье: поле ссылки соответствует второму аргументу get-rss-entry). Он проверяет ленивую последовательность, возвращаемую get-rss-feeds. Для оценки каждого элемента требуется HTTP-запрос по сети для получения нового rss-канала. Чтобы свести к минимуму количество HTTP-запросов, важно проанализировать последовательность один за другим и остановить, как только будет соответствие.

Вот код:

(defn get-rss-entry
  [feeds url]
  (ffirst (drop-while empty? (map #(entry-with-url % url) feeds))))

entry-with-url возвращает ленивую последовательность совпадений или пустую последовательность, если нет совпадения.

Я тестировал это и, похоже, работал правильно (оценивая один URL канала одновременно). Но я обеспокоен тем, что где-то он начнет вести себя "коротким" способом, и он начнет оценивать 32 канала за раз. Я знаю, что существует способ избегать короткого поведения, как обсуждалось здесь, но в этом случае это не кажется даже обязательным.

Я использую ленивый seq не-идиоматически? Будет ли цикл/повторение лучшим вариантом?

Ответы

Ответ 1

В зависимости от неопределенности Chunking кажется неразумным, как вы упомянули выше. Явно "un chunking" в тех случаях, когда вы действительно нуждаетесь в нем, чтобы не быть помеченным, также разумно, потому что тогда, если в какой-то другой момент ваш код изменится так, что это будет означать, что он не сломается. В другой заметке , если вам нужно, чтобы действия были последовательными, агенты - отличный инструмент, вы могли бы отправлять функции загрузки агенту, тогда они будут запускаться по одному за раз и только один раз, независимо от того, как вы оцениваете функция. В какой-то момент вы можете захотеть pmap вашей последовательности, а затем даже un-chunking не будет работать, хотя использование атома будет продолжать работать правильно.

Ответ 2

Вы правы, чтобы беспокоиться. Ваш get-rss-entry действительно вызовет entry-with-url более строго, если параметр feeds представляет собой коллекцию, которая возвращает фрагментированные секвенции. Например, если feeds - вектор, map будет работать на целых кусках за раз.

Эта проблема рассматривается непосредственно в Радости Fogus Clojure, с функцией seq1, определенной в главе 12:

(defn seq1 [s]
  (lazy-seq
    (when-let [[x] (seq s)]
      (cons x (seq1 (rest s)))))) 

Вы можете использовать это право там, где знаете, что вам нужна самая лень, прямо перед тем, как позвонить entry-with-url:

(defn get-rss-entry
  [feeds url]
  (ffirst (drop-while empty? (map #(entry-with-url % url) (seq1 feeds)))))

Ответ 3

Lazy seqs не всегда chunked - это зависит от того, как они создаются.

Например, ленивый seq, создаваемый этой функцией, не разбивается:

(defn integers-from [n]
  (lazy-seq (cons n (do (print \.) (integers-from (inc n))))))

(take 3 (integers-from 3))
=> (..3 .4 5)

Но многие другие встроенные функции clojure производят фрагментированные секвенции по причинам производительности (например, range)