2012-02-20 76 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調用中。