(conj collection item)
增加item
至collection
。要做到這一點,它需要實現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方法調用時會發生什麼? (如何將Cons
和LazySeq
對象一起工作來形成惰性序列將在下面變得更加清晰。)看看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
對象不已緩存的長度 - 如果他們做到了,他們怎麼可能被用於實現懶(甚至是無限的)序列?如果您嘗試使用Cons
的count
,則它只會在其尾部調用count
,然後將結果遞增1.
對於其他來此的人:我的回答非常詳細(可能太詳細),可能是混亂。讓我重複這裏的要點:'conj'的語義要求它根據集合類型改變它的行爲。這需要在集合對象上使用(多態)方法調用。 LazySeq通過委託給它的內部值來處理該方法調用,這需要實現內部值。相反,cons的語義並不要求它調用集合中的任何方法;它只需將其存儲在「Cons」對象的一個字段中。 –
答案的最後三個段落特別有用。有些人可能想先閱讀。 – Mars