17

我的印象是惰性序列总是被分块的。

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

正如预期的那样,打印了 32 个点,因为返回的惰性序列range被分块为 32 个元素块。但是,当range我不是用自己的函数尝试这个时get-rss-feeds,惰性序列不再被分块:

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

只打印了一个点,所以我猜返回的lazy-seqget-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查找特定条目(其 :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如果没有匹配,则返回惰性匹配序列或空序列。

我对此进行了测试,它似乎可以正常工作(一次评估一个提要网址)。但我担心在某个地方,它会以某种“笨重”的方式开始表现,它会开始一次评估 32 个提要。我知道有一种方法可以避免此处讨论的笨拙行为,但在这种情况下似乎甚至不需要。

我是否以非惯用方式使用惰性序列?循环/重复会是更好的选择吗?

4

3 回答 3

11

你的担心是对的。如果参数是返回分块序列的集合,您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
  [饲料网址]
  (ffirst (drop-while empty? (map #(entry-with-url % url) ( seq1 feeds)))))
于 2012-12-27T19:13:26.323 回答
5

惰性序列并不总是分块的——这取决于它们是如何产生的。

例如,这个函数产生的惰性序列是不分块的:

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

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

但是出于性能原因(例如range),许多其他 clojure 内置函数确实会生成分块序列

于 2012-09-18T15:44:25.287 回答
4

正如您上面提到的,取决于分块的模糊性似乎是不明智的。在您确实需要不进行分块的情况下,明确地“取消分块”也是明智的,因为如果在其他时候您的代码以一种分块的方式发生变化,事情就不会中断。另一方面,如果您需要按顺序执行操作,代理是一个很棒的工具,您可以将下载功能发送给代理,然后无论您如何评估功能,它们都将一次运行一个,并且只运行一次。在某些时候,您可能想要pmap您的序列,然后即使使用原子将继续正常工作,即使取消分块也将不起作用。

于 2012-09-13T18:30:12.920 回答