在Clojure中,懒惰的seqs总是变得笨拙吗?

时间:2012-09-13 17:55:28

标签: clojure lazy-evaluation chunking lazy-sequences

我的印象是懒惰的seqs总是被分块。

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

正如预期的那样打印32个点,因为range返回的lazy seq被分成32个元素块。但是,当我用自己的函数range代替get-rss-feeds时,懒惰的seq不再分块了:

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

只打印了一个点,所以我猜get-rss-feeds返回的lazy-seq没有分块。事实上:

=> (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])))

所以看起来,好奇取决于懒惰的seq是如何产生的。我偷看了函数range的源代码,并且有一些暗示它以“矮胖”的方式实现。所以我对它是如何工作有点困惑。有人可以澄清一下吗?


这就是我需要知道的原因。

我必须遵循以下代码:(get-rss-entry (get-rss-feeds h-res) url)

get-rss-feeds的调用会返回我需要检查的Feed的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返回一个懒惰的匹配序列,如果没有匹配则返回一个空序列。

我对此进行了测试,似乎工作正常(一次评估一个Feed网址)。但是我担心某个地方,某种程度上它会以“粗糙”的方式开始表现,并且它将开始一次评估32个提要。我知道有avoid chunky behavior as discussed here的方法,但在这种情况下似乎甚至不需要。

我是否使用懒惰的seq非惯用语?循环/重复是一个更好的选择吗?

3 个答案:

答案 0 :(得分:11)

你是对的。如果get-rss-entry参数是一个返回chunked seqs的集合,那么entry-with-url确实会更严格地调用feeds。例如,如果feeds是向量,map将一次对整个块进行操作。

这个问题直接在Fogus的欢乐的Clojure 中解决,第12章定义了函数seq1

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

答案 1 :(得分:5)

懒惰的seqs 并不总是分块 - 这取决于它们的制作方式。

例如,此函数生成的lazy 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

答案 2 :(得分:3)

如上所述,取决于Chunking的模糊性似乎是不明智的。在你真正需要它不被分块的情况下明确地“un chunking”也是明智的,因为如果在某些其他方面,你的代码会以一种不会破坏它的方式改变。另一方面,如果你需要连续的动作,代理是一个很好的工具你可以将下载功能发送给代理然后它们将一次只运行一次而不管你如何评估功能。在某些时候,您可能希望pmap您的序列,然后即使取消分块也不会工作,尽管使用原子将继续正常工作。