最后一个懒惰的seq会评估clojure中的所有元素吗?

时间:2011-11-26 18:14:25

标签: clojure lazy-evaluation

我们假设我们有一个昂贵的计算expensive。如果我们认为map产生了一个惰性seq,那么下面是否为映射集合的所有元素或仅针对最后一个元素评估函数expensive

(last
  (map expensive '(1 2 3 4 5)))

即。这会对所有值1..5评估expensive还是仅评估(expensive 5)

3 个答案:

答案 0 :(得分:7)

将评估整个系列。一个简单的测试可以回答你的问题。

=> (defn exp [x]
     (println "ran")
     x)
=> (last
     (map exp '(1 2 3 4 5)))
ran
ran
ran
ran
ran
5

答案 1 :(得分:2)

Clojure中没有随机访问延迟序列。

在某种程度上,你可以认为它们等同于单链表 - 你总是拥有当前元素和一个函数来获得下一个。

所以,即使你只是调用(last some-seq),它也会评估所有序列元素,即使序列是懒惰的。如果序列是有限的并且相当小(如果你没有保持序列的头部在参考文献中)它在记忆方面很好。如您所述,如果用于获取下一个元素的函数很昂贵,则可能会出现执行时间问题。

在这种情况下,您可以妥协,以便使用便宜的函数一直走到最后一个元素:

(last some-seq)

然后仅对该结果应用该函数:

(expensive (last some-seq))

答案 2 :(得分:2)

最后将总是强制评估惰性序列 - 这显然是必要的,因为它需要找到序列的结尾,因此需要在每个位置评估惰性序列。

如果你想在所有个别元素中懒惰,一种方法是创建一系列懒惰序列,如下所示:

(defn expensive [n]
  (do 
    (println "Called expensive function on " n)
    (* n n)))

(def lzy (map #(lazy-seq [(expensive %)]) '(1 2 3 4 5)))

(last lzy)
=> Called expensive function on  5
=> (25)

请注意,在这种情况下,最后仍强制评估顶级延迟序列,但不强制评估其中包含的延迟序列,除了我们提取的最后一个(因为它被打印)由REPL)。