2012-09-12 53 views
22

爲什麼cons在lazy-seq的情況下工作,但conj並沒有?clojure cons vs conj與lazy-seq

這工作:

(defn compound-interest [p i] 
    (cons p (lazy-seq (compound-interest (* p (+ 1 i)) i)))) 

這不(它給出了一個堆棧溢出[1]除外):

(defn compound-interest2 [p i] 
    (conj (lazy-seq (compound-interest2 (* p (+ 1 i)) i)) p)) 

[1]噢哦!在stackoverflow中提出涉及堆棧溢出的問題。

+0

對於其他來此的人:我的回答非常詳細(可能太詳細),可能是混亂。讓我重複這裏的要點:'conj'的語義要求它根據集合類型改變它的行爲。這需要在集合對象上使用(多態)方法調用。 LazySeq通過委託給它的內部值來處理該方法調用,這需要實現內部值。相反,cons的語義並不要求它調用集合中的任何方法;它只需將其存儲在「Cons」對象的一個​​字段中。 –

+0

答案的最後三個段落特別有用。有些人可能想先閱讀。 – Mars

回答

38

(conj collection item)增加itemcollection。要做到這一點,它需要實現collection。 (我將解釋下面的原因。)所以遞歸調用立即發生,而不是被延遲。

(cons item collection)創建一個以item開頭的序列,然後是collection中的所有內容。值得注意的是,不需要需要實現collection。所以遞歸調用將被推遲(因爲使用lazy-seq),直到有人試圖獲得結果序列的尾部。

我會解釋這是如何工作的內部:

cons的實際返回clojure.lang.Cons對象,這是懶惰的序列而成的。 conj返回您通過它的相同類型的集合(無論是列表,矢量還是其他任何內容)。 conj使用集合本身上的多態Java方法調用來執行此操作。 (請參閱line 524 of clojure/src/jvm/clojure/lang/RT.java。)

當在由lazy-seq返回的clojure.lang.LazySeq對象上發生Java方法調用時會發生什麼? (如何將ConsLazySeq對象一起工作來形成惰性序列將在下面變得更加清晰。)看看line 98 of clojure/src/jvm/clojure/lang/LazySeq.java。注意它調用一個名爲seq的方法。這是實現LazySeq的價值(詳情請跳到line 55)。

所以你可以說conj需要確切地知道你通過了什麼樣的收集,但cons沒有。 cons只是要求「收集」參數是ISeq

請注意,Clojure中的Cons對象與其他Lisp中的「cons單元」不同 - 在大多數Lisp中,「cons」只是一個對象,它可以持有2個指向其他任意對象的對象。所以你可以使用cons單元來構建樹,等等。 Clojure Cons以任意Object爲頭,ISeq爲尾。由於Cons本身實現了ISeq,因此可以從Cons對象中構建序列,但它們也可以指向向量或列表等。(請注意,Clojure中的「列表」是特殊類型(PersistentList),並且是而不是構建自Cons對象。)clojure.lang.LazySeq implements ISeq,因此它可以用作Cons的尾部(Lisp中的「cdr」)。A LazySeq持有一些代碼的參考,其中評估ISeq某種類型,但它並沒有實際評估該代碼,直到需要時,並且在它評估代碼後,它緩存返回的ISeq並委託給它。

...這一切都開始有意義嗎?你知道懶序列是如何工作的嗎?基本上,你從LazySeq開始。當LazySeq被實現時,其評估爲Cons,其指向另一LazySeq。當那一個被實現時......你明白了。所以你得到一個LazySeq對象鏈,每個對象持有(並委託給)一個Cons

關於Clojure中「conses」和「lists」的區別,「lists」(PersistentList對象)包含一個緩存的「length」字段,因此他們可以在O(1)時間內響應count。這在其他Lisp中不起作用,因爲在大多數Lisp中,「列表」是可變的。但在Clojure中它們是不可變的,所以緩存長度的作品。 Clojure中

Cons對象不已緩存的長度 - 如果他們做到了,他們怎麼可能被用於實現懶(甚至是無限的)序列?如果您嘗試使用Conscount,則它只會在其尾部調用count,然後將結果遞增1.

+0

你的意思是實現項目:) –

+0

@Ivan:否 - cons和'conj'的參數順序相反。 –

+0

對不起我以爲他寫了'(conj p(lazy-seq(compound-interest2(* p(+ 1 i))i)))'。 –