Clojure

比较器指南

注意:本文档描述了 Clojure 1.10 和 Java 8,但也适用于大多数其他版本。

总结

比较器是一个函数,它接受两个参数 xy,并返回一个值,指示 xy 应该排序的相对顺序。它可以是返回整数的三向比较器,也可以是返回布尔值的二向比较器。请参阅下面的操作,了解根据 xy 的顺序,返回值应该是什么。

在 Clojure 中,您需要比较器来对值集合进行排序,或者以所需的排序顺序维护值集合,例如 sorted-mapsorted-setpriority-map(也称为优先级队列)。

默认比较器 compare 非常适合按升序对数字进行排序,或者按字典序(即字典顺序)对字符串、关键字或符号进行排序,以及其他一些情况。请参阅以下示例和更多详细信息。

如果 compare 没有达到您想要的效果,则必须提供您自己的比较器。下面每个建议将在本文档后面更详细地解释。

操作

  • 确保您的比较器基于您要比较的值的全序关系。它应该能够比较数据集中可能出现的任何一对值,并确定哪个值应该排在前面(或它们是否相等)。

  • 编写三向比较器或布尔比较器

    • 三向比较器接受 2 个值 xy,并返回一个 Java 32 位的 int,如果 xy 之前,则返回负数;如果 xy 之后,则返回正数;如果它们相等,则返回 0。如果您没有理由偏好其他返回值,请使用值 -1、0 和 1。

    • 布尔比较器接受 2 个值 xy,如果 xy 之前,则返回 true,否则返回 false(包括 xy 相等的情况)。<> 是很好的例子。<=>= 不是。性能说明:您的布尔比较器可能会被调用两次以区分“在之后”或“等于”的情况。

  • 通过反转传递给现有比较器的参数的顺序来反转排序。

  • 比较包含“排序键”的等长 Clojure 向量,以便在值之间进行多字段比较。

  • 在对集合进行排序之前,删除或替换数据中“非数字”(##NaN)的出现,并避免将它们用作排序集合中键的一部分。

不要

  • 不要编写一个布尔比较器,如果值相等则返回 true。这样的比较器是不一致的。它会导致排序集合的行为不正确,并且排序会产生不可预测的顺序。

  • 除非您希望在排序集合中最多只出现这两个值中的一个,否则不要对排序集和映射使用将两个值视为相等的比较器。

  • 除非您确实知道自己在做什么,否则不要在编写三向比较器时使用减法。

简介

在这里,我们描述了函数 compare 提供的默认排序顺序。之后,我们将提供其他比较器的示例,以及在编写您自己的比较器时应遵循的一些准则和应避免的错误。

Clojure 的默认比较器

如果您没有指定自己的比较器,则排序将由内置函数 compare 完成。compare 适用于许多类型的值,并以一种特定的方式对它们进行排序

  • 数字按升序排序,如果两个数字在数值上相等(由 == 判断),则返回 0,即使 = 返回 false 也是如此。例外:即使对于所有数字 x(包括 ##NaN 本身),(== ##NaN x) 都为 false,但 (compare ##NaN x) 对所有数字 x 都为 0。

  • 字符串按字典序(也称为字典顺序)排序,其依据是它们作为 UTF-16 代码单元序列的表示形式。对于仅限于 ASCII 子集的字符串,这表示字母顺序(区分大小写)。

  • 符号首先按其命名空间排序(如果存在),如果它们具有相同的命名空间,则按其名称排序。命名空间和名称都按其字符串表示形式进行比较,即字典序。所有没有命名空间的符号都排在任何有命名空间的符号之前。

  • 关键字的排序方式与符号相同,但如果您尝试将关键字与符号进行比较,则会抛出异常。

  • 向量从元素最少的到元素最多的进行排序,等长向量之间按字典序排序。

  • Clojure ref 按创建顺序排序。

  • 所有实现了 Comparable 接口的 Java 类型,例如字符、布尔值、File、URI 和 UUID,都通过其 compareTo 方法进行比较。

  • nil:可以与上面所有值进行比较,并且被认为小于其他任何值。

如果给定两个类型“过于不同”的值,compare 会抛出异常,例如,它可以将整数、长整数和双精度数相互比较,但不能将字符串与关键字或关键字与符号进行比较。它不能比较列表、序列、集或映射。

下面使用 sortsorted-setsorted-map 的示例都使用了默认比较器。

user> (sort [22/7 2.71828 ##-Inf 1 55 3N])
(##-Inf 1 2.71828 3N 22/7 55)

user> (sorted-set "aardvark" "boo" "a" "Antelope" "bar")
#{"Antelope" "a" "aardvark" "bar" "boo"}

user> (sorted-set 'user/foo 'clojure.core/pprint 'bar 'clojure.core/apply 'user/zz)
#{bar clojure.core/apply clojure.core/pprint user/foo user/zz}

user> (sorted-map :map-key 10, :amp [3 2 1], :blammo "kaboom")
{:amp [3 2 1], :blammo "kaboom", :map-key 10}

user> (sort [[-8 2 5] [-5 -1 20] [1 2] [1 -5] [10000]])
([10000] [1 -5] [1 2] [-8 2 5] [-5 -1 20])

user> (import '(java.util UUID))
java.util.UUID

user> (sort [(UUID. 0xa 0) (UUID. 5 0x11) (UUID. 5 0xb)])
(#uuid "00000000-0000-0005-0000-00000000000b"
 #uuid "00000000-0000-0005-0000-000000000011"
 #uuid "00000000-0000-000a-0000-000000000000")

user> (sort [:ns2/kw1 :ns2/kw2 :ns1/kw2 :kw2 nil])
(nil :kw2 :ns1/kw2 :ns2/kw1 :ns2/kw2)

如果您使用不同类型的参数调用 compare,则会抛出异常。上面任何数值类型都可以相互比较,但不能与非数值类型比较。如果您对列表、集、映射或上面未提到的任何其他类型使用 compare,也会抛出异常。如果您希望对这些值进行排序,则必须实现您自己的比较器。

现成的比较器

首先考虑使用其他人开发的经过良好测试的比较器,尤其是在它们比较复杂的情况下。

一个完美的例子是根据不同语言环境对不同语言的 Unicode 字符串进行排序。Java 的 Collator 类和 ICU(Unicode 国际组件)提供了用于此目的的库。

编写您自己的比较器

反序

要按降序对数字进行排序,只需编写一个比较器,将参数按相反的顺序传递给 compare 即可

user> (sort [4 2 3 1])
(1 2 3 4)

user> (defn reverse-cmp [a b]
        (compare b a))
#'user/reverse-cmp

user> (sort reverse-cmp [4 3 2 1])
(4 3 2 1)

此类简短函数通常使用 Clojure 的 #() 表示法编写,其中两个参数分别为 %1 和 %2,按此顺序。

user> (sort #(compare %2 %1) [4 3 2 1])

reverse-cmp 也适用于 compare 支持的所有其他类型。

多字段比较器

因为等长 Clojure 向量按字典序比较,所以它们可以用于对映射或记录等值进行多字段排序。只有当字段已经按您希望的顺序(或该顺序的反序)由 compare 排序时,这才能起作用。

首先,我们将展示一种不比较向量的方法。

(def john1 {:name "John", :salary 35000.00, :company "Acme"})
(def mary  {:name "Mary", :salary 35000.00, :company "Mars Inc"})
(def john2 {:name "John", :salary 40000.00, :company "Venus Co"})
(def john3 {:name "John", :salary 30000.00, :company "Asteroids-R-Us"})
(def people [john1 mary john2 john3])

(defn by-salary-name-co [x y]
  ;; :salary values sorted in decreasing order because x and y
  ;; swapped in this compare.
  (let [c (compare (:salary y) (:salary x))]
    (if (not= c 0)
      c
      ;; :name and :company are sorted in increasing order
      (let [c (compare (:name x) (:name y))]
        (if (not= c 0)
          c
          (let [c (compare (:company x) (:company y))]
            c))))))

user> (pprint (sort by-salary-name-co people))
({:name "John", :salary 40000.0, :company "Venus Co"}
 {:name "John", :salary 35000.0, :company "Acme"}
 {:name "Mary", :salary 35000.0, :company "Mars Inc"}
 {:name "John", :salary 30000.0, :company "Asteroids-R-Us"})

以下是通过比较 Clojure 向量实现的简短方法。它的行为与上面完全相同。请注意,与上面一样,字段 :salary 按降序排序,因为 xy 已交换。

(defn by-salary-name-co2 [x y]
    (compare [(:salary y) (:name x) (:company x)]
             [(:salary x) (:name y) (:company y)]))

user> (pprint (sort by-salary-name-co2 people))
({:name "John", :salary 40000.0, :company "Venus Co"}
 {:name "John", :salary 35000.0, :company "Acme"}
 {:name "Mary", :salary 35000.0, :company "Mars Inc"}
 {:name "John", :salary 30000.0, :company "Asteroids-R-Us"})

以上方法适用于从要排序的值中计算成本不高的键值。如果键值计算成本很高,最好为每个值计算一次。请参阅 sort-by 文档中描述的“装饰-排序-取消装饰”技术。

布尔比较器

Java 比较器都是三向的,这意味着它们根据第一个参数是否应该被认为小于、等于或大于第二个参数返回负数、0 或正数。

在 Clojure 中,您也可以使用布尔比较器,如果第一个参数应该排在第二个参数之前,则返回 true,否则返回 false(即应该排在后面,或它们相等)。函数 < 是一个完美的例子,只要您只需要比较数字即可。> 用于按降序对数字进行排序。在幕后,当这样的 Clojure 函数 bool-cmp-fn“作为比较器调用”时,Clojure 会运行类似于以下代码的代码以返回 int

(if (bool-cmp-fn x y)
  -1     ; x < y
  (if (bool-cmp-fn y x)  ; note the reversed argument order
    1    ; x > y
    0))  ; x = y

您可以通过调用任何 Clojure 函数的 compare 方法来查看这一点。下面是一个使用 < 的自定义版本 my-< 的示例,它在被调用时会打印其参数,因此您可以看到它被调用多次的情况

user> (defn my-< [a b]
        (println "(my-<" a b ") returns " (< a b))
        (< a b))
#'user/my-<

;; (. o (compare a b)) calls the method named compare for object
;; o, with arguments a and b.  In this case the object is the
;; Clojure function my-<
user> (. my-< (compare 1 2))
(my-< 1 2 ) returns  true
-1
user> (. my-< (compare 2 1))
(my-< 2 1 ) returns  false
(my-< 1 2 ) returns  true
1
user> (. my-< (compare 1 1))
(my-< 1 1 ) returns  false
(my-< 1 1 ) returns  false
0

;; Calling a Clojure function in the normal way uses its invoke
;; method, not compare.
user> (. my-< (invoke 2 1))
(my-< 2 1 ) returns  false
false

如果您想要了解所有详细信息,请参阅 Clojure 源文件 src/jvm/clojure/lang/AFunction.java 中的 compare 方法。

比较器的通用规则

任何比较器,无论是三向的还是布尔型的,都应该返回与您要比较的值的全序关系一致的答案。

全序关系简单来说就是对所有值从最小到最大进行排序,其中一些值组可以彼此相等。每对值都必须彼此可比较(即比较器不会返回“我不知道如何比较它们”的答案)。

例如,您可以按照数学中通常的方式,将所有以m/n形式(其中m和n为整数)表示的分数从最小到最大排序。许多分数将彼此相等,例如1/2 = 2/4 = 3/6。实现该全序关系的比较器应将其视为相同。

三向比较器(cmp a b)应该分别返回负数、正数或0的int类型值,如果ab之前、之后或在全序关系中被认为等于b

布尔比较器(cmp a b)应该在a在全序关系中位于b之前时返回true,或者在ab之后或被认为等于b时返回false。也就是说,它应该像数字的<一样工作。如后所述,它不应该像数字的<=一样工作(参见“排序集合和映射的比较器很容易出错”部分)。

避免的错误

注意排序集合中“非数字”值作为比较值

Clojure的默认比较器compare将“非数字”(##NaN)值视为与所有其他数字相等。如果您对包含##NaN的数字序列调用sort,它可能会抛出异常。

user> (sort [##NaN 5 13 ##NaN 3 7 12 ##NaN 8 4 2 20 6 9 ##NaN 50 83 19 -7 0 18 26 30 42 ##NaN 57 90 -8 -12 43 87 38])
Execution error (IllegalArgumentException) at java.util.TimSort/mergeHi (TimSort.java:899).
Comparison method violates its general contract!

即使它没有抛出异常,返回的序列也可能未排序。这是因为compare没有像比较器应该做的那样,将##NaN与其他数字放在全序关系中,以便sort能够正确工作。

user> (sort [##NaN 10 5 13 ##NaN 3 7 12 ##NaN 8 4 2 20 6 9 ##NaN 50 83 19 -7])
(##NaN -7 2 3 4 5 6 7 8 10 12 13 ##NaN ##NaN 9 19 20 ##NaN 50 83)

因为##NaN不等于任何其他值,所以您不能使用以下代码从数字序列中删除值

user> (remove #(= % ##NaN) [9 3 ##NaN 4])
(9 3 ##NaN 4)

您可以使用函数NaN?来确定一个值是否为##NaN。函数NaN?是在Clojure 1.11.0版本中添加的。您可以使用Java方法Double/isNaN与任何版本的Clojure一起使用。

user> (remove NaN? [9 3 ##NaN 4])
(9 3 4)
user> (remove #(Double/isNaN %) [9 3 ##NaN 4])
(9 3 4)

排序集合和映射的比较器很容易出错

这也可以准确地表述为“比较器很容易出错”,但在您对排序集合和映射使用错误的比较器时,通常会更加明显。如果您编写了本节中提到的那种错误的比较器并使用它们来调用sort,通常不会出现任何问题(尽管不一致的比较器也不适合排序)。对于排序集合和映射,这些错误的比较器可能导致值无法添加到您的排序集合中,或者被添加但无法在您搜索时找到它们。

假设您想要一个包含两个元素的向量的排序集合,其中每个元素都是一个字符串后跟一个数字,例如["a" 5]。您希望集合按数字排序,并允许具有相同数字但不同字符串的多个向量。您的第一次尝试可能是编写类似by-2nd的东西。

(defn by-2nd [a b]
  (compare (second a) (second b)))

但是,当您尝试添加具有相同数字的多个向量时会发生什么。

user> (sorted-set-by by-2nd ["a" 1] ["b" 1] ["c" 1])
#{["a" 1]}

集合中只有一个元素,因为by-2nd将所有三个向量视为相等。集合不应该包含重复的元素,因此其他元素不会被添加。

在这种情况下,一个常见的思路是使用基于<=而不是<的布尔比较器函数。

(defn by-2nd-<= [a b]
  (<= (second a) (second b)))

布尔比较器by-2nd-<=在创建集合的第一步似乎工作正常,但在测试元素是否在集合中时失败了。

user> (def sset (sorted-set-by by-2nd-<= ["a" 1] ["b" 1] ["c" 1]))
#'user/sset
user> sset
#{["c" 1] ["b" 1] ["a" 1]}
user> (sset ["c" 1])
nil
user> (sset ["b" 1])
nil
user> (sset ["a" 1])
nil

这里的问题是by-2nd-<=给出了不一致的答案。如果您询问["c" 1]是否在["b" 1]之前,它会返回true(Clojure的布尔到整数比较器转换将其转换为-1)。如果您询问["b" 1]是否在["c" 1]之前,它也会返回true(同样被Clojure转换为-1)。如果给它一个不一致的比较器,没有人能够合理地期望排序数据结构的实现对其行为提供任何保证。

上面“多字段比较器”中描述的技术为本示例提供了正确的比较器。一般来说,请注意只比较值的部分内容。考虑在您感兴趣的所有字段都比较完之后,加入某种类型的断开关系条件。

旁注:如果您不想在集合中包含多个具有相同数字的向量,那么by-2nd就是您应该使用的比较器。它给出了您想要的确切行为。(待定:这里是否有任何注意事项?sorted-set是否会出于任何原因使用=来比较元素,或者只使用提供的比较器函数?)

小心在比较器中使用减法

Java比较器如果第一个参数被视为小于第二个参数,则返回一个负整数;如果第一个参数被视为大于第二个参数,则返回一个正整数;如果它们相等,则返回0。

user> (compare 10 20)
-1
user> (compare 20 10)
1
user> (compare 20 20)
0

因此,您可能会想要通过从一个数值中减去另一个数值来编写比较器,如下所示。

user> (sort #(- %1 %2) [4 2 3 1])
(1 2 3 4)

虽然这在许多情况下都能正常工作,但在使用此技术之前,请三思而后行。使用显式的条件检查并返回-1、0或1,或者使用布尔比较器,会减少错误的发生。

为什么?Java比较器必须返回32位的int类型,因此当Clojure函数用作比较器并返回任何类型的数字时,该数字会在后台使用Java方法intValue转换为int。如果您想了解详细信息,请参阅Clojure源文件src/jvm/clojure/lang/AFunction.java方法compare

对于比较浮点数和比率,这会导致差异小于1的数字被视为相等,因为-1到1之间的返回值会被截断为int类型的0。

;; This gives the correct answer
user> (sort #(- %1 %2) [10.0 9.0 8.0 7.0])
(7.0 8.0 9.0 10.0)

;; but this does not, because all values are treated as equal by
;; the bad comparator.
user> (sort #(- %1 %2) [1.0 0.9 0.8 0.7])
(1.0 0.9 0.8 0.7)

;; .intValue converts all values between -1.0 and 1.0 to 0
user> (map #(.intValue %) [-1.0 -0.99 -0.1 0.1 0.99 1.0])
(-1 0 0 0 0 1)

当比较其差异在截断为32位int类型时(通过丢弃除了最低32位之外的所有位)发生符号变化的整数值时,这也会导致错误。大约一半的长整数值对在使用减法作为比较器时会被错误地比较。

;; This looks good
user> (sort #(- %1 %2) [4 2 3 1])
(1 2 3 4)

;; What the heck?
user> (sort #(- %1 %2) [2147483650 2147483651 2147483652 4 2 3 1])
(3 4 2147483650 2147483651 2147483652 1 2)

user> [Integer/MIN_VALUE Integer/MAX_VALUE]
[-2147483648 2147483647]

;; How .intValue truncates a few selected values.  Note especially
;; the first and last ones.
user> (map #(.intValue %) [-2147483649 -2147483648 -1 0 1
                            2147483647  2147483648])
(2147483647 -2147483648 -1 0 1 2147483647 -2147483648)

Java本身对字符串和字符等使用减法比较器。这不会导致任何问题,因为将任意一对16位字符转换为整数后相减的结果保证在int范围内,不会发生溢出。如果您的比较器不能保证提供这种受限的输入,最好不要冒险。

跨不同类型的比较器

有时您可能希望按某个键对值的集合进行排序,但该键不是唯一的。您希望具有相同键的值以某种可预测、可重复的顺序排序,但您不太关心该顺序是什么。

作为一个玩具示例,您可能有一个向量集合,每个向量有两个元素,其中第一个元素始终是一个字符串,第二个元素始终是一个数字。您希望按数字值升序对它们进行排序,但您知道您的数据可能包含多个具有相同数字的向量。您希望以某种方式打破平局,并在多次排序中保持一致。

此案例可以使用前面部分中描述的多字段比较器轻松实现。

(defn by-number-then-string [[a-str a-num] [b-str b-num]]
  (compare [a-num a-str]
           [b-num b-str]))

如果整个向量值可以使用compare进行比较,因为所有向量都具有相同的长度,并且每个对应元素的类型可以使用compare进行相互比较,那么您也可以这样做,使用整个向量值作为最终的断开关系条件。

(defn by-number-then-whatever [a-vec b-vec]
  (compare [(second a-vec) a-vec]
           [(second b-vec) b-vec]))

但是,如果向量中某些元素位置包含对于compare无法处理的类型,并且这些向量具有相同的第二个元素,则这将抛出异常。

;; compare throws exception if you try to compare a string and a
;; keyword
user> (sort by-number-then-whatever [["a" 2] ["c" 3] [:b 2]])
Execution error (ClassCastException) at user/by-number-then-whatever (REPL:2).
class java.lang.String cannot be cast to class clojure.lang.Keyword

以下cc-cmp(“跨类比较”)在这种情况下可能很有用。它可以比较不同类型的值,它根据表示值类型的字符串对它们进行排序。它不仅仅是(class x),因为那样的话,像IntegerLong这样的数字将不会按数值顺序排序。库clj-arrangement也可能对您有所帮助。

;; comparison-class throws exceptions for some types that might be
;; useful to include.

(defn comparison-class [x]
  (cond (nil? x) ""
        ;; Lump all numbers together since Clojure's compare can
        ;; compare them all to each other sensibly.
        (number? x) "java.lang.Number"

        ;; sequential? includes lists, conses, vectors, and seqs of
        ;; just about any collection, although it is recommended not
        ;; to use this to compare seqs of unordered collections like
        ;; sets or maps (vectors should be OK).  This should be
        ;; everything we would want to compare using cmp-seq-lexi
        ;; below.  TBD: Does it leave anything out?  Include anything
        ;; it should not?
        (sequential? x) "clojure.lang.Sequential"

        (set? x) "clojure.lang.IPersistentSet"
        (map? x) "clojure.lang.IPersistentMap"
        (.isArray (class x)) "java.util.Arrays"

        ;; Comparable includes Boolean, Character, String, Clojure
        ;; refs, and many others.
        (instance? Comparable x) (.getName (class x))
        :else (throw
               (ex-info (format "cc-cmp does not implement comparison of values with class %s"
                                (.getName (class x)))
                        {:value x}))))

(defn cmp-seq-lexi
  [cmpf x y]
  (loop [x x
         y y]
    (if (seq x)
      (if (seq y)
        (let [c (cmpf (first x) (first y))]
          (if (zero? c)
            (recur (rest x) (rest y))
            c))
        ;; else we reached end of y first, so x > y
        1)
      (if (seq y)
        ;; we reached end of x first, so x < y
        -1
        ;; Sequences contain same elements.  x = y
        0))))

;; The same result can be obtained by calling cmp-seq-lexi on two
;; vectors, but cmp-vec-lexi should allocate less memory comparing
;; vectors.
(defn cmp-vec-lexi
  [cmpf x y]
  (let [x-len (count x)
        y-len (count y)
        len (min x-len y-len)]
    (loop [i 0]
      (if (== i len)
        ;; If all elements 0..(len-1) are same, shorter vector comes
        ;; first.
        (compare x-len y-len)
        (let [c (cmpf (x i) (y i))]
          (if (zero? c)
            (recur (inc i))
            c))))))

(defn cmp-array-lexi
  [cmpf x y]
  (let [x-len (alength x)
        y-len (alength y)
        len (min x-len y-len)]
    (loop [i 0]
      (if (== i len)
        ;; If all elements 0..(len-1) are same, shorter array comes
        ;; first.
        (compare x-len y-len)
        (let [c (cmpf (aget x i) (aget y i))]
          (if (zero? c)
            (recur (inc i))
            c))))))


(defn cc-cmp
  [x y]
  (let [x-cls (comparison-class x)
        y-cls (comparison-class y)
        c (compare x-cls y-cls)]
    (cond (not= c 0) c  ; different classes

          ;; Compare sets to each other as sequences, with elements in
          ;; sorted order.
          (= x-cls "clojure.lang.IPersistentSet")
          (cmp-seq-lexi cc-cmp (sort cc-cmp x) (sort cc-cmp y))

          ;; Compare maps to each other as sequences of [key val]
          ;; pairs, with pairs in order sorted by key.
          (= x-cls "clojure.lang.IPersistentMap")
          (cmp-seq-lexi cc-cmp
                        (sort-by key cc-cmp (seq x))
                        (sort-by key cc-cmp (seq y)))

          (= x-cls "java.util.Arrays")
          (cmp-array-lexi cc-cmp x y)

          ;; Make a special check for two vectors, since cmp-vec-lexi
          ;; should allocate less memory comparing them than
          ;; cmp-seq-lexi.  Both here and for comparing sequences, we
          ;; must use cc-cmp recursively on the elements, because if
          ;; we used compare we would lose the ability to compare
          ;; elements with different types.
          (and (vector? x) (vector? y)) (cmp-vec-lexi cc-cmp x y)

          ;; This will compare any two sequences, if they are not both
          ;; vectors, e.g. a vector and a list will be compared here.
          (= x-cls "clojure.lang.Sequential")
          (cmp-seq-lexi cc-cmp x y)

          :else (compare x y))))

这是一个快速示例,演示了cc-cmp比较不同类型值的能力。

user> (pprint (sort cc-cmp [true false nil Double/MAX_VALUE 10
                            Integer/MIN_VALUE :a "b" 'c (ref 5)
                            [5 4 3] '(5 4) (seq [5]) (cons 6 '(1))
                            #{1 2 3} #{2 1}
                            {:a 1, :b 2} {:a 1, :b -2}
                            (object-array [1 2 3 4])]))
(nil
 {:a 1, :b -2}
 {:a 1, :b 2}
 #{1 2}
 #{1 2 3}
 :a
 #<Ref@1493d9b3: 5>
 (5)
 (5 4)
 [5 4 3]
 (6 1)
 c
 false
 true
 -2147483648
 10
 1.7976931348623157E308
 "b"
 [1, 2, 3, 4])
nil

原始作者:Andy Fingerhut