2013-01-02 47 views
7

雖然我熟悉函數式語言,主要是Scala,但我對Clojure相當陌生。關於Clojure集合的操作

我想弄清楚在Clojure中對集合進行操作的慣用方式是什麼。我對map等功能的行爲特別困惑。

在Scala中,一個非常小心的做,這樣map總是會返還相同種類的原始集合的集合,只要這是有道理的:

List(1, 2, 3) map (2 *) == List(2, 4, 6) 
Set(1, 2, 3) map (2 *) == Set(2, 4, 6) 
Vector(1, 2, 3) map (2 *) == Vector(2, 4, 6) 

相反,Clojure中,就我所知,大多數操作(如mapfilter)都是懶惰的,即使在急切的數據結構上調用時也是如此。這有奇怪的結果

(map #(* 2 %) [1 2 3]) 

lazy-list,而不是一個向量。

雖然我更喜歡懶惰的操作,但我發現上面的問題很混亂。事實上,媒介保證了列表中沒有的某些性能特徵。

說我使用上面的結果並追加它的結尾。如果我理解正確,結果不會被評估,直到我試圖追加它,然後它被評估,我得到一個列表,而不是一個向量;所以我必須遍歷它才能追加到最後。之後我可以把它變成矢量,但是這會變得混亂,可以被忽略。

如果我理解正確,map是多態的,實現它不會是一個問題,以便它返回向量上的向量,列表上的列表,流上的流(此時帶有惰性語義)等等。我想我錯過了Clojure及其成語的基本設計。

什麼是Clojure的數據結構的基本操作的原因不preverse結構?

+0

看看地圖的源代碼。地圖不關心集合的類型。您可以在地圖上建立一個記住集合類型的宏,並在最後將集合轉換爲該類型。 https://github.com/clojure/clojure/blob/master/src/clj/clojure/core/reducers.clj –

+1

在https://github.com/上查看clojure.algo.generic.functor/fmap保留輸入類型的'map'實現的clojure/algo.generic。 – Alex

回答

7

在Clojure中,許多函數都基於Seq抽象。 這種方法的好處是,您不必爲每種不同的集合類型編寫一個函數 - 只要您的集合可以被視爲一個序列(具有頭部和尾部的東西),就可以將它用於所有的seq功能。帶有seqs和輸出序列的函數比可將其用於特定集合類型的函數更加可組合,因此可重用。當你在seq上編寫你自己的函數時,你不需要處理如下特殊情況:如果用戶給我一個向量,我必須返回一個向量,等等。你的函數在seq管道內和其他任何東西一樣好seq功能。

地圖返回懶惰seq的原因是設計選擇。在Clojure懶惰是許多這些功能結構的默認設置。如果你想有其他行爲,如沒有中間集合並行,看看減速機庫:http://clojure.com/blog/2012/05/08/reducers-a-library-and-model-for-collection-processing.html

至於性能也越高,地圖總是以應用功能n次的集合,從第一個到最後一個元素,所以它的性能總是O(n)或更差。在這種情況下,向量或列表沒有區別。懶惰會給你帶來的好處是,你只會消耗列表的第一部分。如果你必須在地圖輸出結尾附加一些東西,矢量確實更有效率。在這種情況下,您可以使用mapv(在Clojure 1.4中添加):它需要一個集合並輸出一個向量。我會說,如果你有一個很好的理由,只擔心這些性能優化。大多數時候這是不值得的。

瞭解更多關於SEQ抽象這裏:http://clojure.org/sequences

這是Clojure中添加1.4 filterv另一種載體,返回高階函數。

+1

我不會說列表vs.矢量不會影響性能 - 這取決於您打算如何使用「地圖」的結果 - 例如'(第n個(地圖#(* 2%)真長矢量)10000)' – Alex

+0

@Alex,你是對的,我已經改變了我的答案,然後再發布此評論 –

+1

另一點是創建序列非常便宜;創造載體雖然仍然便宜,但實際上更昂貴。 'map'禮貌地做廉價的事情,如果你出於某種原因需要它,讓它在之後變成矢量。而且:如果你有一個你想要映射的矢量,那麼通常你只需要一個序列,而不是一個矢量。 – amalloy