Clojure

資料結構

Clojure 具有豐富的資料結構。它們共用一組屬性

  • 它們是不可變的

  • 它們是可讀的

  • 它們在 equals 的實作中支援適當的值相等語意

  • 它們提供良好的雜湊值

  • 此外,集合

    • 透過介面進行操作。

    • 支援排序

    • 支援持續操作。

    • 支援元資料

    • 實作 java.lang.Iterable

    • 實作 java.util.Collection 或 java.util.Map 的非選用 (唯讀) 部分

nil

nil 是 Clojure 中任何資料類型的可能值。nil 與 Java null 具有相同的值。Clojure 條件系統以 nil 和 false 為基礎,其中 nil 和 false 表示條件測試中的邏輯假值 - 其他任何值都是邏輯真值。此外,nil 用作序列協定中的序列結束哨兵值。

數字

Clojure 預設完全支援 JVM 原生值,允許為數值應用程式編寫高效能、慣用的 Clojure 程式碼。

Clojure 也支援衍生自 java.lang.Number 的 Java 封裝數字類型,包括 BigInteger 和 BigDecimal,以及其自己的 Ratio 類型。有一些特殊處理

長整數

預設情況下,Clojure 使用 Java 的 long 原生類型實例來操作自然數。當原生整數運算產生一個值,而該值太大而無法包含在原生值中時,會擲出 java.lang.ArithmeticException。Clojure 提供了一組以撇號為後綴的替代數學運算子:+'、-'、*'、inc' 和 dec'。這些運算子在溢位時會自動提升為 BigInt,但效率低於常規數學運算子。

比率

表示整數之間的比率。無法簡化為整數的整數除法會產生一個比率,例如 22/7 = 22/7,而不是浮點數或截斷值。

傳染性

BigInt 和浮點數類型在運算中具有「傳染性」。也就是說,任何涉及 BigInt 的整數運算都會產生 BigInt,而任何涉及 double 或 float 的運算都會產生 double。

BigInt 和 BigDecimal 文字

BigInt 和 BigDecimal 的數字文字分別使用後綴 N 和 M 指定。

範例表達式 傳回值

(== 1 1.0 1M)

true

(/ 2 3)

2/3

(/ 2.0 3)

0.6666666666666666

(map #(Math/abs %) (range -3 3))

(3 2 1 0 1 2)

(class 36786883868216818816N)

clojure.lang.BigInt

(class 3.14159265358M)

java.math.BigDecimal

運算: + - * / inc dec quot rem min max
自動提升運算: +' -' *' inc' dec'
比較: == < <= > >= zero? pos? neg?
位元操作: bit-and bit-or bit-xor bit-not bit-shift-right bit-shift-left
比率: numerator denominator
轉換: int bigdec bigint double float long num short

字串

Clojure 字串是 Java 字串。另請參閱 列印

user=> (map (fn [x] (.toUpperCase x)) (.split "Dasher Dancer Prancer" " "))
("DASHER" "DANCER" "PRANCER")

字元

Clojure 字元為 Java 字元。

關鍵字

關鍵字為會評估為本身的符號識別碼。它們提供非常快速的相等性測試。如同符號,它們有名稱和可選的 命名空間,兩者皆為字串。開頭的「:」不屬於命名空間或名稱。

關鍵字實作 IFn,可呼叫一個參數(一個映射)並提供一個可選的第二個參數(一個預設值)。例如,(:mykey my-hash-map :none) 的意思與 (get my-hash-map :mykey :none) 相同。請參閱 get

符號

符號為識別碼,通常用於參照其他東西。它們可以在程式表單中用於參照函式參數、let 繫結、類別名稱和全域變數。它們有名稱和可選的 命名空間,兩者皆為字串。符號可以有元資料(請參閱 with-meta)。

符號就像關鍵字一樣,實作 IFn,可呼叫一個參數(一個映射)並提供一個可選的第二個參數(一個預設值)。例如,('mysym my-hash-map :none) 的意思與 (get my-hash-map 'mysym :none) 相同。請參閱 get

symbol symbol? gensym(另請參閱 #- 後綴 reader 巨集)

集合

所有 Clojure 集合都是不可變且 持續的。特別是,Clojure 集合透過使用結構性共用來支援有效建立「修改過」的版本,並對持續使用做出所有效能界限保證。這些集合是有效率且本質上執行緒安全的。集合由抽象表示,可能有一個或多個具體實現。特別是,由於「修改」操作會產生新的集合,因此新的集合可能與來源集合沒有相同的具體類型,但會有相同的邏輯(介面)類型。

所有集合都支援 count 來取得集合的大小、conj 來「新增」到集合,以及 seq 來取得可以遍歷整個集合的序列,儘管它們的具體行為對於不同類型的集合略有不同。

由於集合支援 seq 函數,所有 序列函數 都可以用於任何集合。

Java 集合雜湊

Java 集合介面指定 清單集合映射 計算 hashCode() 值的演算法。所有 Clojure 集合都在其 hashCode() 實作中符合這些規範。

Clojure 集合雜湊

Clojure 提供自己的雜湊運算,為集合(和其他類型)提供更好的雜湊屬性,稱為 hasheq 值。

IHashEq 介面標記提供 hasheq() 函數以取得 hasheq 值的集合。在 Clojure 中,hash 函數可用於計算 hasheq 值。

已排序集合(向量、清單、序列等)必須使用下列演算法來計算 hasheq(其中 hash 計算 hasheq)。請注意,unchecked-add-int 和 unchecked-multiply-int 用於取得整數溢位計算。

(defn hash-ordered [collection]
  (-> (reduce (fn [acc e] (unchecked-add-int
                            (unchecked-multiply-int 31 acc)
                            (hash e)))
              1
              collection)
      (mix-collection-hash (count collection))))

未排序集合(映射、集合)必須使用下列演算法來計算 hasheq。映射條目視為鍵值的有序集合。請注意,unchecked-add-int 用於取得整數溢位計算。

(defn hash-unordered [collection]
  (-> (reduce unchecked-add-int 0 (map hash collection))
      (mix-collection-hash (count collection))))

mix-collection-hash 演算法是實作細節,可能會變更。

清單 (IPersistentList)

清單是集合。它們直接實作 ISeq 介面。(請注意,空清單也實作 ISeq,但 seq 函數永遠會傳回 nil 給空序列。)count 為 O(1)。conj 會將項目置於清單的最前面。

建立清單:list list*
將清單視為堆疊:peek pop
檢查清單:list?

向量 (IPersistentVector)

向量是依連續整數索引的值集合。向量支援以 log32N 跳躍方式依索引存取項目。count 為 O(1)。conj 會將項目置於向量的尾端。向量也支援 rseq,用於以反向順序傳回項目。向量實作 IFn,用於單一引數的 invoke(),它們假設引數為索引,並像 nth 一樣在自身中查詢,亦即向量是其索引的函式。向量的比較方式首先依長度,然後依序比較每個元素。

建立向量:vector vec vector-of
檢查向量:get nth peek rseq vector?
「變更」向量:assoc pop subvec replace

另請參閱 zippers

映射 (IPersistentMap)

Map 是一種將鍵對應到值的集合。提供兩種不同的 map 類型 - 哈希和排序。哈希 map 需要正確支援 hashCode 和 equals 的鍵。排序 map 需要實作 Comparable 或 Comparator 執行個體的鍵。哈希 map 提供較快的存取(log32N 跳躍)相對於(logN 跳躍),但排序 map 是經過排序的。count 為 O(1)。conj 預期另一個(可能是單一項目)map 作為項目,並傳回一個新的 map,它是舊的 map 加上新 map 的項目,這可能會覆寫舊的項目。conj 也接受 MapEntry 或兩個項目的向量(鍵和值)。seq 傳回 map 項目序列,它們是鍵/值對。排序 map 也支援 rseq,它傳回反向順序的項目。Map 實作 IFn,用於 invoke() 一個引數(一個鍵)並有一個選用的第二個引數(一個預設值),也就是說,map 是其鍵的函數。nil 鍵和值是可以的。

建立一個新的 map:hash-map sorted-map sorted-map-by
「變更」一個 map:assoc dissoc select-keys merge merge-with zipmap
檢查一個 map:get contains? find keys vals map?
檢查一個 map 項目:key val

StructMaps

現在,大多數 StructMaps 的用途都更適合使用 記錄

通常,許多映射實例具有相同的基礎鍵集,例如在將映射用作結構或物件時,在其他語言中會使用這些結構或物件。StructMaps 支援此用例,方法是有效地共享鍵資訊,同時也提供對這些鍵的效能增強存取器(選用)。StructMaps 在各方面都是映射,支援相同的函數集,可與所有其他映射互通,且可持續擴充(即結構映射不限於其基礎鍵)。唯一的限制是您無法將結構映射與其基礎鍵之一分離。結構映射將按順序保留其基礎鍵。

先使用 create-structdefstruct 建立結構基礎物件,然後使用 struct-mapstruct 建立實例,即可建立 StructMaps。

(defstruct desilu :fred :ricky)
(def x (map (fn [n]
              (struct-map desilu
                :fred n
                :ricky 2
                :lucy 3
                :ethel 4))
             (range 100000)))
(def fred (accessor desilu :fred))
(reduce (fn [n y] (+ n (:fred y))) 0 x)
 -> 4999950000
(reduce (fn [n y] (+ n (fred y))) 0 x)
 -> 4999950000

StructMap 設定:create-struct defstruct accessor
建立個別結構:struct-map struct

ArrayMaps

在執行程式碼表單操作時,通常需要一個維護鍵順序的映射。陣列映射就是這樣的映射 - 它僅實作為鍵值鍵值…​ 的陣列。因此,它具有線性查詢效能,僅適用於非常小的映射。它實作完整的映射介面。可以使用 array-map 函數建立新的 ArrayMaps。請注意,僅在未「修改」時,陣列映射才會維護排序順序。後續的 assoc-ing 最終會導致它「變成」雜湊映射。

集合

集合是唯一值集合。

對雜湊集合有文字支援

#{:a :b :c :d}
-> #{:d :a :b :c}

您可以使用 hash-setsorted-set 函數建立集合

(hash-set :a :b :c :d)
-> #{:d :a :b :c}

(sorted-set :a :b :c :d)
-> #{:a :b :c :d}

您也可以使用 set 函數取得集合中值的集合

(set [1 2 3 2 1 2 3])
-> #{1 2 3}

集合是集合

(def s #{:a :b :c :d})
(conj s :e)
-> #{:d :a :b :e :c}

(count s)
-> 4

(seq s)
-> (:d :a :b :c)

(= (conj s :e) #{:a :b :c :d :e})
-> true

集合支援使用 disj 進行「移除」,以及 contains?get,後者會傳回與鍵相等的集合中所包含的物件(如果找到的話)

(disj s :d)
-> #{:a :b :c}

(contains? s :b)
-> true

(get s :a)
-> :a

集合是其成員的函數,使用取得

(s :b)
-> :b

(s :k)
-> nil

Clojure 提供基本集合操作,例如 聯集 / 差集 / 交集,以及一些偽關聯代數支援,用於「關係」,它們只是映射的集合 - 選擇 / 索引 / 重新命名 / 聯結