[英]How to create a lazy-seq generating, anonymous recursive function in Clojure?
[英]How to understand clojure's lazy-seq
我試圖理解clojure的lazy-seq
運算符,以及懶惰評估的概念。 我知道這個概念背后的基本思想:表達式的評估會延遲到需要的值。
一般來說,這可以通過兩種方式實現:
使用惰性求值技術,可以構造被評估為已消耗的無限數據結構。 這些無限序列利用lambdas,閉包和遞歸。 在clojure中,使用lazy-seq
和cons
形式生成這些無限數據結構。
我想了解lazy-seq
是如何產生魔力的。 我知道它實際上是一個宏。 請考慮以下示例。
(defn rep [n]
(lazy-seq (cons n (rep n))))
這里, rep
函數返回一個LazySeq
評估的LazySeq
類型LazySeq
,現在可以使用序列API對其進行轉換和消費(從而進行求值)。 此API提供函數take
, map
, filter
和reduce
。
在擴展形式中,我們可以看到lambda如何用於存儲單元格的配方而不立即進行評估。
(defn rep [n]
(new clojure.lang.LazySeq (fn* [] (cons n (rep n)))))
LazySeq
一起LazySeq
? (reduce + (take 3 (map inc (rep 5))))
map
應用於序列 , take
限制序列和 reduce
評估序列 ? 此外, 這些功能如何與Vector
或LazySeq
?
此外, 是否可以生成嵌套的無限數據結構 ?:包含列表的列表,包含列表,包含列表......無限寬和深,評估為序列API消耗?
最后一個問題,這是否存在實際差異
(defn rep [n]
(lazy-seq (cons n (rep n))))
這個 ?
(defn rep [n]
(cons n (lazy-seq (rep n))))
這是很多問題!
如果你看一下LazySeq
的類源代碼,你會發現它實現了ISeq
接口,提供了first
, more
和next
。
像map
, take
和filter
這樣的函數是使用lazy-seq
構建的(它們生成延遲序列)和first
和rest
(反過來使用more
),這就是它們如何使用lazy seq作為它們的輸入集合 - 通過使用first
和more
實現LazySeq
類。
(reduce + (take 3 (map inc (rep 5))))
關鍵是看看LazySeq.first
是如何工作的。 它將調用包裝函數來獲取和記憶結果。 在您的情況下,它將是以下代碼:
(cons n (rep n))
因此,它將是一個cons單元,其中n
為其值,另一個LazySeq
實例(對rep
的遞歸調用的結果)作為rest
部分。 它將成為此LazySeq
對象的實現值,並first
返回緩存的cons單元格的值。
當你對它進行more
調用時,它將以相同的方式確保實現特定LazySeq
對象的值(或重用的memoized值)並在其上調用more
(在這種情況下more
在包含另一個LazySeq
對象的cons單元格上)。
一旦你獲得的另一個實例LazySeq
對象,具有more
,當你調用這個故事重復first
就可以了。
map
和take
將創建另一個lazy-seq
,它將first
調用並將more
的集合作為參數傳遞(只是另一個懶惰的seq),所以它將是類似的故事。 的差將是僅在傳遞到值如何cons
生成(例如主叫f
到由所獲得的值first
所調用LazySeq
在映射超過值map
,而不是原始值等n
在rep
功能)。
隨着reduce
它有點簡單,因為它會用loop
與first
和more
遍歷輸入序列懶惰和應用還原作用,產生最終結果。
由於實際的實現看起來像map
和take
我鼓勵你檢查它們的源代碼 - 它很容易理解。
如上所述, map
, take
和其他功能在first
和rest
方面起作用(提醒 - rest
是在more
上實現的)。 因此,我們需要解釋first
和rest
/ more
如何使用不同的集合類型:它們檢查集合是否實現ISeq
(然后它直接實現這些函數),或者他們嘗試創建集合的seq
視圖並將其實現first
甚至more
。
這絕對是可能的,但我不確定您希望得到的確切數據形狀。 你的意思是得到一個懶的seq,它生成另一個序列作為它的值(而不是像你的rep
n
那樣的單個值)但是將它作為一個平坦的序列返回?
(defn nested-cons [n]
(lazy-seq (cons (repeat n n) (nested-cons (inc n)))))
(take 3 (nested-cons 1))
;; => ((1) (2 2) (3 3 3))
寧願回來(1 2 2 3 3 3)
?
對於這種情況,您可以使用concat
而不是cons
來創建兩個或更多序列的延遲序列:
(defn nested-concat [n]
(lazy-seq (concat (repeat n n) (nested-concat (inc n)))))
(take 6 (nested-concat 1))
;; => (1 2 2 3 3 3)
(defn rep [n]
(lazy-seq (cons n (rep n))))
還有這個?
(defn rep [n]
(cons n (lazy-seq (rep n))))
在這個特殊情況下並不是真的。 但是在cons單元格沒有包裝原始值而是函數調用的結果來計算它的情況下,后一種形式不是完全懶惰的。 例如:
(defn calculate-sth [n]
(println "Calculating" n)
n)
(defn rep1 [n]
(lazy-seq (cons (calculate-sth n) (rep1 (inc n)))))
(defn rep2 [n]
(cons (calculate-sth n) (lazy-seq (rep2 (inc n)))))
(take 0 (rep1 1))
;; => ()
(take 0 (rep2 1))
;; Prints: Calculating 1
;; => ()
因此,后一種形式將評估其第一個元素,即使您可能不需要它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.