【问题标题】:In Clojure, are lazy seqs always chunked?在 Clojure 中,惰性 seq 总是分块吗?
【发布时间】:2012-09-06 21:10:02
【问题描述】:

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

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

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

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

只打印了一个点,所以我猜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 的调用查找特定条目(其 :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 个提要。我知道有一种方法可以avoid chunky behavior as discussed here,但在这种情况下似乎甚至不需要它。

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

【问题讨论】:

  • 如果您使用clojure.core 中的各种块函数和/或您的序列实现IChunkIChunkedSeq 接口,则似乎只有“分块”序列。目前(在 1.4.0 中),这些没有记录。
  • 你用的是什么版本的clojure?

标签: clojure lazy-evaluation chunking lazy-sequences


【解决方案1】:

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

【讨论】:

  • 您能否用示例代码的草图对此进行扩展?你的意思是代理而不是原子?
  • 你的意思是代理而不是原子吗?因为提供了交换功能!将重试
  • s/atom/agent/g 对此感到抱歉。我的手指出卖了我的大脑并按下了错误的键......修复了。
【解决方案2】:

惰性序列并不总是分块 - 这取决于它们的生成方式。

比如这个函数产生的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

【讨论】:

  • 补充一点很重要,mapfilter 都可能产生分块序列。混合副作用和懒惰是微妙错误的秘诀。传感器在这里提供帮助。
【解决方案3】:

你的担心是对的。如果feeds 参数是一个返回分块序列的集合,那么您的get-rss-entry 确实会调用entry-with-url。例如,如果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)))))

【讨论】:

  • 非常感谢。顺便说一句,刚读完这本书,它使我的 Clojure 游戏更上一层楼。等不及更新版本了。
  • 值得指出的是,这个对seq1 的非分块调用必须在源代码 完成。例如,如果你从 map 接收到一个惰性序列而不是一个分块序列,那么你就不走运了 - 无论你做什么,map 都会向前看。
猜你喜欢
  • 2012-06-17
  • 1970-01-01
  • 1970-01-01
  • 2012-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多