簡體   English   中英

如何理解clojure的懶惰seq

[英]How to understand clojure's lazy-seq

我試圖理解clojure的lazy-seq運算符,以及懶惰評估的概念。 我知道這個概念背后的基本思想:表達式的評估會延遲到需要的值。

一般來說,這可以通過兩種方式實現:

  • 在編譯時使用宏或特殊形式;
  • 在運行時使用lambda函數

使用惰性求值技術,可以構造被評估為已消耗的無限數據結構。 這些無限序列利用lambdas,閉包和遞歸。 在clojure中,使用lazy-seqcons形式生成這些無限數據結構。

我想了解lazy-seq是如何產生魔力的。 我知道它實際上是一個宏。 請考慮以下示例。

(defn rep [n]
  (lazy-seq (cons n (rep n))))

這里, rep函數返回一個LazySeq評估的LazySeq類型LazySeq ,現在可以使用序列API對其進行轉換和消費(從而進行求值)。 此API提供函數takemapfilterreduce

在擴展形式中,我們可以看到lambda如何用於存儲單元格的配方而不立即進行評估。

(defn rep [n]
  (new clojure.lang.LazySeq (fn* [] (cons n (rep n))))) 
  • 序列API如何實際與LazySeq一起LazySeq
  • 以下表達式實際發生了什么

(reduce + (take 3 (map inc (rep 5))))

  • 如何將中間操作map應用於序列
  • 如何take限制序列
  • 終端操作如何reduce評估序列

此外, 這些功能如何與VectorLazySeq

此外, 是否可以生成嵌套的無限數據結構 ?:包含列表的列表,包含列表,包含列表......無限寬和深,評估為序列API消耗?

最后一個問題,這是否存在實際差異

(defn rep [n]
  (lazy-seq (cons n (rep n))))

這個

(defn rep [n]
  (cons n (lazy-seq (rep n))))

這是很多問題!

seq API如何與LazySeq實際配合使用?

如果你看一下LazySeq的類源代碼,你會發現它實現了ISeq接口,提供了firstmorenext

maptakefilter這樣的函數是使用lazy-seq構建的(它們生成延遲序列)和firstrest (反過來使用more ),這就是它們如何使用lazy seq作為它們的輸入集合 - 通過使用firstmore實現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就可以了。

maptake將創建另一個lazy-seq ,它將first調用並將more的集合作為參數傳遞(只是另一個懶惰的seq),所以它將是類似的故事。 的差將是僅在傳遞到值如何cons生成(例如主叫f到由所獲得的值first所調用LazySeq在映射超過值map ,而不是原始值等nrep功能)。

隨着reduce它有點簡單,因為它會用loopfirstmore遍歷輸入序列懶惰和應用還原作用,產生最終結果。

由於實際的實現看起來像maptake我鼓勵你檢查它們的源代碼 - 它很容易理解。

seq API如何適用於不同的集合類型(例如lazy seq和persistent vector)?

如上所述, maptake和其他功能在firstrest方面起作用(提醒 - rest是在more上實現的)。 因此,我們需要解釋firstrest / 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM