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

Geo*_*o G 15 clojure lazy-evaluation chunking lazy-sequences

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

=> (take 1 (map #(do (print \.) %) (range)))
(................................0)
Run Code Online (Sandbox Code Playgroud)

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

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

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

=> (chunked-seq? (seq (range)))
true

=> (chunked-seq? (seq (get-rss-feeds r)))
false
Run Code Online (Sandbox Code Playgroud)

以下是来源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])))
Run Code Online (Sandbox Code Playgroud)

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


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

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

调用get-rss-feeds返回我需要检查的一系列URL的延迟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))))
Run Code Online (Sandbox Code Playgroud)

entry-with-url 如果没有匹配,则返回惰性匹配序列或空序列.

我测试了它,它似乎正常工作(一次评估一个提要网址).但是我担心某个地方,某种程度上它会以"粗糙"的方式开始表现,并且它将开始一次评估32个提要.我知道有一种方法可以避免这里讨论的粗短行为,但在这种情况下似乎甚至不需要.

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

Cho*_*ser 11

你是对的.如果参数是一个返回chunked seqs的集合,那么你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)))))) 
Run Code Online (Sandbox Code Playgroud)

在你打电话之前,你可以在你知道你想要最懒惰的地方使用这个entry-with-url:

(defn get-rss-entry
  [feeds url]
  (ffirst (drop-while empty? (map #(entry-with-url % url) (seq1 feeds)))))


mik*_*era 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)
Run Code Online (Sandbox Code Playgroud)

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

  • 需要补充的是,“map”和“filter”都可能产生分块的 seq,这一点非常重要。副作用和懒惰的混合会导致微妙的错误。传感器在这里提供帮助。 (2认同)

Art*_*ldt 4

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

  • 您能否通过示例代码草图对此进行扩展?你的意思是代理而不是原子吗? (2认同)