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

字符串

Clojure 字符串是 Java 字符串。另请参见 打印

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

字符

Clojure 字符是 Java 字符。

关键字

关键字是符号标识符,它们求值自身。它们提供非常快速的相等性测试。与符号一样,它们具有名称和可选的 命名空间,两者都是字符串。开头的 ':' 不属于命名空间或名称的一部分。

关键字为单个参数(映射)的 invoke() 实现 IFn,可选地接受第二个参数(默认值)。例如 (:mykey my-hash-map :none)(get my-hash-map :mykey :none) 的含义相同。参见 get

符号

符号是标识符,通常用于引用其他事物。它们可以用于程序形式来引用函数参数、let 绑定、类名和全局 var。它们具有名称和可选的 命名空间,两者都是字符串。符号可以有元数据(参见 with-meta)。

符号与关键字一样,为单个参数(映射)的 invoke() 实现 IFn,可选地接受第二个参数(默认值)。例如 ('mysym my-hash-map :none)(get my-hash-map 'mysym :none) 的含义相同。参见 get

symbol symbol? gensym(另请参见 #- 后缀 阅读器 宏)

集合

所有 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)

映射是将键映射到值的集合。提供了两种不同的映射类型 - 哈希和排序。哈希映射需要正确支持 hashCode 和 equals 的键。排序映射需要实现 Comparable 的键,或 Comparator 的实例。哈希映射提供更快的访问(log32N 跳跃)与(logN 跳跃),但排序映射是,排序的。count 是 O(1)。conj 将另一个(可能是单个条目)映射作为项目,并返回一个新映射,该映射是旧映射加上新映射的条目,这可能会覆盖旧映射的条目。conj 还接受 MapEntry 或两个项目(键和值)的向量。seq 返回映射条目的序列,它们是键/值对。排序映射还支持 rseq,它以相反顺序返回条目。映射实现 IFn,用于一个参数的 invoke()(一个键)以及可选的第二个参数(一个默认值),即映射是其键的函数。nil 键和值是可以的。

创建新映射:hash-map sorted-map sorted-map-by
'更改' 映射:assoc dissoc select-keys merge merge-with zipmap
检查映射:get contains? find keys vals map?
检查映射条目:key val

结构映射

大多数 StructMaps 的使用现在将通过 记录 获得更好的服务。

通常,许多映射实例具有相同的基本键集,例如当映射用作结构或对象时,会在其他语言中。StructMaps 支持此用例,通过有效地共享键信息,同时还提供对这些键的可选增强性能访问器。StructMaps 在所有方面都是映射,支持相同的功能集,与所有其他映射互操作,并且是持久可扩展的(即,StructMaps 不限于其基本键)。唯一的限制是您不能从 StructMaps 中分离其基本键之一。StructMaps 将按顺序保留其基本键。

StructMaps 是通过首先使用 create-structdefstruct 创建结构基础对象,然后使用 struct-mapstruct 创建实例来创建的。

(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

数组映射

在进行代码形式操作时,通常需要一个映射,它可以保持键顺序。数组映射就是这样的映射 - 它只是实现为一个键 val 键 val…​ 的数组。因此,它具有线性查找性能,仅适用于非常小的映射。它实现了完整的映射接口。可以使用 array-map 函数创建新的数组映射。请注意,只有在未“修改”时,数组映射才会保持排序顺序。后续的 assoc 操作最终会导致它“变为”哈希映射。

集合

集合是唯一值的集合。

哈希集合有文字支持

#{: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

集合是其成员的函数,使用get

(s :b)
-> :b

(s :k)
-> nil

Clojure 提供了基本的集合操作,如 union / difference / intersection,以及一些针对“关系”的伪关系代数支持,这些关系只是映射集 - select / index / rename / join