2012-02-20 78 views
4

我很难理解懒惰。Clojure中最简单的懒惰功能

有人可以帮助我了解为什么我下面的功能更是不可以偷懒

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

回答

8

filter懒惰来自呼叫filter在递归循环的if分支中。这就是代码碰到另一个lazy-seq的地方,并停止评估seq直到请求另一个元素。

你实现my-red命中lazy-seq唯一一次通话,这意味着你的序列要么根本不评估或评估完全。

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 []))))) 

基本上,你包装(懒惰-SEQ)。

2

全部lazy-seq确实是采取它的参数和延迟执行它。为了产生一个真正的懒惰序列,每个链接都必须包装在一个懒惰的seq调用中。懒惰的“粒度”是在拨打lazy-seq的电话之间完成了多少工作。解决这个问题的一种方法是使用返回懒惰seq的更高级别的函数。

此外,尾递归和懒惰是相互排斥的。这不会导致堆栈溢出,因为在每一步中,递归调用都会被封装到一个懒惰的seq中并返回。如果调用者然后尝试评估lazy seq,则调用递归调用,但它由序列函数的原始调用者调用,而不是序列函数本身调用,这意味着堆栈不会增长。这与通过蹦床实现优化尾部递归的想法有些相似(参见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调用中。