В 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)