Всегда ли в Clojure ленивые последовательности разбиваются на части?

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

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

Как и ожидалось, печатаются 32 точки, потому что ленивый seq, возвращаемый range, разбивается на фрагменты из 32 элементов. Однако, когда вместо range я пробую это с моей собственной функцией get-rss-feeds, ленивый 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])))

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


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

У меня есть следующий код: (get-rss-entry (get-rss-feeds h-res) url)

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

Вызов get-rss-entry ищет конкретную запись (чье поле: link совпадает со вторым аргументом 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 канала одновременно. Я знаю, что есть способ избегайте кратковременного поведения, как описано здесь, но в данном случае это даже не требуется.

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


person Geo G    schedule 13.09.2012    source источник
comment
Похоже, что последовательность разбивается на фрагменты только в том случае, если вы используете различные функции фрагментов в clojure.core и / или ваша последовательность реализует интерфейсы IChunk и IChunkedSeq. В настоящее время (в версии 1.4.0) они недокументированы.   -  person noahlz    schedule 13.09.2012
comment
какую версию clojure вы используете?   -  person Arthur Ulfeldt    schedule 13.09.2012


Ответы (3)


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

person Arthur Ulfeldt    schedule 13.09.2012
comment
Не могли бы вы расширить это, приведя набросок образца кода? Вы имеете в виду агентов вместо атомов? - person noahlz; 13.09.2012
comment
Вы имеете в виду агент, а не атом? потому что функции предоставлены для обмена! будет повторена - person noisesmith; 09.02.2015
comment
s / atom / agent / g извините за это. Мои пальцы выдают мой мозг и нажимают не те клавиши ... исправлено. - person Arthur Ulfeldt; 10.02.2015

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

Эта проблема решается непосредственно в Joy of Clojure Fogus с помощью функции 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)))))
person Chouser    schedule 27.12.2012
comment
Спасибо большое. Кстати, только что дочитал книгу, и моя игра Clojure вышла на новый уровень. Не могу дождаться обновленной версии. - person Geo G; 18.02.2013
comment
Стоит отметить, что этот неизбирательный вызов seq1 должен выполняться в источнике. Например, если вы получаете ленивую последовательность от map по фрагментированной последовательности, вам не повезло - map будет смотреть вперед, что бы вы ни делали. - person Thom; 02.02.2015

Ленивые последовательности не всегда разбиваются на части - это зависит от того, как они создаются.

Например, ленивый 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)

person mikera    schedule 18.09.2012
comment
Очень важно добавить, что как map, так и filter могут создавать сегментированные последовательности. Смешение побочных эффектов и лени - рецепт для скрытых ошибок. Здесь помогают преобразователи. - person event_jr; 02.08.2017