Clojure中最简单的懒惰功能

时间:2012-02-20 13:43:27

标签: clojure lazy-evaluation

我很难理解懒惰。

有人可以帮助我理解为什么我的下面的功能不是懒惰的

(defn my-red
    ([f coll] (my-red f (first coll) (rest coll) ))
    ([f init coll] 
      (let [mr (fn [g i c d] 
            (if (empty? c) d 
        (recur  g (g  i (first c)) (rest c)  (conj d (g  i (first c)) ))))] 
    (lazy-seq (mr f init coll [])))))

而在clojure.org/lazy上给出的这个例子是

(defn filter
  "Returns a lazy sequence of the items in coll for which
  (pred item) returns true. pred must be free of side-effects."
  [pred coll]
  (let [step (fn [p c]
                 (when-let [s (seq c)]
                   (if (p (first s))
                     (cons (first s) (filter p (rest s)))
                     (recur p (rest s)))))]
    (lazy-seq (step pred coll))))

3 个答案:

答案 0 :(得分:8)

filter中的懒惰来自对递归循环的filter分支中if的调用。这就是代码命中另一个lazy-seq并停止评估seq直到请求另一个元素。

my-red的实施只能呼叫lazy-seq一次,这意味着你的seq根本没有被评估或被完全评估。

答案 1 :(得分:2)

mr函数将重复整个coll。也许你的缩进误导了你。正确缩进,并删除了一些无用的参数,该函数看起来像这样:

(defn my-red
  ([f coll] (my-red f (first coll) (rest coll) ))
  ([f init coll] 
     (let [mr (fn [i c d] 
                (if (empty? c)
                  d 
                  (recur (f i (first c))
                         (rest c)
                         (conj d (f i (first c))))))] 
       (lazy-seq (mr init coll [])))))

基本上,你在mr函数周围包装(lazy-seq),它在一个大的recur循环中完成所有工作。

答案 2 :(得分:2)

所有lazy-seq都会采用其参数并延迟执行它。为了产生真正的延迟序列,每个链接必须包含在lazy-seq调用中。懒惰的“粒度”是对lazy-seq的调用之间的工作量。解决这个问题的一种方法是使用返回惰性seq的更高级函数。

此外,尾递归和懒惰是互斥的。这不会导致堆栈溢出,因为在每一步,递归调用都会被包装到一个惰性seq中并返回。如果调用者然后尝试计算延迟seq,则调用递归调用,但序列函数的原始调用者调用它,而不是序列函数本身,这意味着堆栈不会增长。这有点类似于通过trampolines实现优化尾递归的想法(参见Clojure的trampoline函数)。

这是一个懒惰的版本:

(defn my-red
  ([f coll] (my-red f (first coll) (rest coll) ))
  ([f init coll] 
   (let [mr (fn mr [g i c] 
              (if (empty? c)
                nil
                (let [gi (g  i (first c))]
                  (lazy-seq (cons gi (mr g gi (rest c)))))))] 
     (lazy-seq (mr f init coll)))))

注意mr的每次运行如何立即返回nil或lazy seq,递归调用来自lazy-seq调用。