Clojure

相等性

本文件探討 Clojure 中相等性的概念,包括函數 ===identical?,以及它們與 Java 的 equals 方法有何不同。它也對 Clojure 的 hash 以及它與 Java 的 hashCode 有何不同進行了一些說明。本指南的開頭提供了一個最重要的資訊摘要,供快速參考,然後對詳細資訊進行更廣泛的檢閱。

除非另有說明,否則本指南中的資訊說明 Clojure 1.10.0 的行為。

摘要

比較表示相同值的不可變值,或比較是相同物件的可變物件時,Clojure 的 = 為真。為了方便,如果用於比較 Java 集合彼此之間,或與 Clojure 的不可變集合比較,且其內容相等時,= 也會傳回真。但是,如果您使用非 Clojure 集合,則有重要的注意事項。

如果下列情況屬實,則呼叫兩個不可變的純量值時,Clojure 的 = 為真:

  • 兩個參數都是 niltruefalse、相同的字元或相同的字串(即相同的字元序列)。

  • 兩個參數都是符號,或兩個都是關鍵字,具有相同的命名空間和名稱。

  • 兩個參數都是相同「類別」中的數字,並且在數值上相同,其中類別之一為

    • 整數或比率

    • 浮點數(float 或 double)

    • BigDecimal.

當使用兩個集合呼叫時,Clojure 的 = 為 true,如果

  • 兩個參數都是順序(序列、清單、向量、佇列或實作 java.util.List 的 Java 集合),其中 = 元素以相同的順序排列。

  • 兩個參數都是集合(包括實作 java.util.Set 的 Java 集合),其中 = 元素,忽略順序。

  • 兩個參數都是映射(包括實作 java.util.Map 的 Java 映射),其中 =值,忽略輸入順序。

  • 兩個參數都是使用 defrecord 建立的記錄,其中 =值,忽略順序,而且它們具有相同的類型。將記錄與映射進行比較時,= 會傳回 false,無論其鍵和值為何,因為它們沒有相同的類型。

當使用兩個可變 Clojure 物件(例如變數、參照、原子或代理)或兩個「待處理」Clojure 物件(例如未來、承諾或延遲)呼叫時,Clojure 的 = 為 true,如果

  • 兩個參數是相同的物件,即 (identical? x y) 為 true。

對於所有其他類型

  • 兩個參數都是使用 deftype 定義的相同類型。呼叫該類型的 equiv 方法,其傳回值會成為 (= x y) 的值。

  • 對於其他類型,Java 的 x.equals(y) 為 true。

Clojure 的 == 特別適用於數值

  • == 可用於不同數字類別的數字(例如整數 0 和浮點數 0.0)。

  • 如果任何要比較的數值不是數字,則會擲回例外。

如果您使用超過兩個參數呼叫 ===,則當所有連續對為 === 時,結果將為 true。hash= 一致,但有以下例外。

例外或可能的驚喜

  • 在 Clojure 基於雜湊的集合(例如映射鍵或集合元素)中使用非 Clojure 集合時,由於雜湊行為不同,它不會等於具有 Clojure 對應項的類似集合。(請參閱 相等性和雜湊CLJ-1372

  • 使用 = 比較集合時,集合中的數字也會使用 = 進行比較,因此上述三個數字類別非常重要。

  • 「非數字」值 ##NaNFloat/NaNDouble/NaN 不等於 === 任何東西,甚至它們自己也不等於。建議:避免將 ##NaN 包含在 Clojure 資料結構中,您想使用 = 將它們彼此比較,有時會得到 true 作為結果。

  • 0.0 等於 = -0.0

  • Clojure 正規表示式,例如 #"a.*bc",是使用 Java java.util.regex.Pattern 物件實作的,而 Java 在兩個 Pattern 物件上的 equals 會傳回 (identical? re1 re2),即使它們被記錄為不可變物件。因此 (= #"abc" #"abc") 會傳回 false,而 = 僅在兩個正規表示式碰巧是記憶體中相同的物件時才會傳回 true。建議:避免在 Clojure 資料結構中使用正規表示式實例,您想使用 = 將它們彼此比較,即使正規表示式實例不是相同的物件,也會得到 true 作為結果。如果您覺得有需要,請考慮先將它們轉換為字串,例如 (str #"abc")"abc"(請參閱 CLJ-1182

  • Clojure 持久佇列永不 = 於實作 java.util.List 的 Java 集合,即使它們按相同順序具有 = 元素(請參閱 CLJ-1059

  • 使用 = 比較排序的映射和另一個映射,其中 compare 在比較它們的鍵時會擲回例外,因為它們具有不同的類型(例如關鍵字與數字),在某些情況下會擲回例外(請參閱 CLJ-2325

在大多數情況下,hash= 一致,表示:如果 (= x y),則 (= (hash x) (hash y))。對於任何不符合此條件的值或物件,基於 hash 的 Clojure 集合將無法正確找到或移除那些項目,即對於具有那些項目作為元素的基於 hash 的集合,或具有那些項目作為鍵的基於 hash 的映射。

  • hash 與數字的 = 一致,特殊浮點值和雙精度值除外。建議:使用 (double x) 將浮點數轉換為雙精度數,以避免此問題。

  • hash 與不可變 Clojure 集合及其非 Clojure 對應項不一致。有關更多詳細資訊,請參閱 等式和 hash 部分。建議:在將非 Clojure 集合包含在其他 Clojure 資料結構中之前,請將它們轉換為其 Clojure 不可變對應項。

  • 歷史:hash 與具有類別 VecSeq 的物件的 = 不一致,這些物件從呼叫中傳回,例如 (seq (vector-of :int 0 1 2)),直到在 Clojure 1.10.2 中修復(請參閱 CLJ-1364

簡介

Clojure 中的等式最常使用 = 測試。

user> (= 2 (+ 1 1))
true
user> (= (str "fo" "od") "food")
true

與 Java 的 equals 方法不同,Clojure 的 = 對許多不具有相同類型的值傳回 true。

user> (= (float 314.0) (double 314.0))
true
user> (= 3 3N)
true

= 在兩個數字具有相同數值時並非總是傳回 true。

user> (= 2 2.0)
false

如果您想測試不同數值類別的數值相等性,請使用 ==。有關詳細資訊,請參閱以下 數字 部分。

具有相同順序中相等元素的順序集合(序列、向量、清單和佇列)是相等的

user> (range 3)
(0 1 2)
user> (= [0 1 2] (range 3))
true
user> (= [0 1 2] '(0 1 2))
true
;; not = because different order
user> (= [0 1 2] [0 2 1])
false
;; not = because different number of elements
user> (= [0 1] [0 1 2])
false
;; not = because 2 and 2.0 are not =
user> (= '(0 1 2) '(0 1 2.0))
false

如果兩個集合具有相等的元素,則它們是相等的。集合通常是無序的,但即使使用已排序的集合,在比較相等性時也不會考慮排序順序。

user> (def s1 #{1999 2001 3001})
#'user/s1
user> s1
#{2001 1999 3001}
user> (def s2 (sorted-set 1999 2001 3001))
#'user/s2
user> s2
#{1999 2001 3001}
user> (= s1 s2)
true

如果兩個映射具有相同的鍵組,並且每個鍵都對應到每個映射中的相等值,則它們是相等的。與集合一樣,映射是無序的,並且在已排序的映射中不會考慮排序順序。

user> (def m1 (sorted-map-by > 3 -7 5 10 15 20))
#'user/m1
user> (def m2 {3 -7, 5 10, 15 20})
#'user/m2
user> m1
{15 20, 5 10, 3 -7}
user> m2
{3 -7, 5 10, 15 20}
user> (= m1 m2)
true

請注意,儘管向量已編制索引並具有一些類似映射的品質,但映射和向量在 Clojure 中絕不會比較為 =

user> (def v1 ["a" "b" "c"])
#'user/v1
user> (def m1 {0 "a" 1 "b" 2 "c"})
#'user/m1
user> (v1 0)
"a"
user> (m1 0)
"a"
user> (= v1 m1)
false

在比較 Clojure 集合時,會忽略與它們相關聯的任何元資料。

user> (def s1 (with-meta #{1 2 3} {:key1 "set 1"}))
#'user/s1
user> (def s2 (with-meta #{1 2 3} {:key1 "set 2 here"}))
#'user/s2
user> (binding [*print-meta* true] (pr-str s1))
"^{:key1 \"set 1\"} #{1 2 3}"
user> (binding [*print-meta* true] (pr-str s2))
"^{:key1 \"set 2 here\"} #{1 2 3}"
user> (= s1 s2)
true
user> (= (meta s1) (meta s2))
false

使用 defrecord 建立的記錄在許多方面與 Clojure 映射的行為類似。但是,它們僅 = 於相同類型的其他記錄,並且僅當它們具有相同的鍵和相同的值時才相等。它們永遠不會等於映射,即使它們具有相同的鍵和值。

當您定義 Clojure 記錄時,您這樣做是為了建立一個可以與其他類型區分的不同類型 — 您希望每個類型都有自己的行為,並使用 Clojure 協定和多重方法。

user=> (defrecord MyRec1 [a b])
user.MyRec1
user=> (def r1 (->MyRec1 1 2))
#'user/r1
user=> r1
#user.MyRec1{:a 1, :b 2}

user=> (defrecord MyRec2 [a b])
user.MyRec2
user=> (def r2 (->MyRec2 1 2))
#'user/r2
user=> r2
#user.MyRec2{:a 1, :b 2}

user=> (def m1 {:a 1 :b 2})
#'user/m1

user=> (= r1 r2)
false             ; r1 and r2 have different types
user=> (= r1 m1)
false             ; r1 and m1 have different types
user=> (into {} r1)
{:a 1, :b 2}      ; this is one way to "convert" a record to a map
user=> (= (into {} r1) m1)
true              ; the resulting map is = to m1

除了數字和 Clojure 集合之外,Clojure = 的行為與 Java 的 equals 相同。

布林值和字元在相等性方面很簡單。

字串也很簡單,但在某些涉及 Unicode 的情況下除外,其中由不同的 Unicode 字元序列組成的字串在顯示時可能看起來相同,而在某些應用程式中應視為相等,即使 = 傳回 false。如果您有興趣,請參閱 Wikipedia 頁面上的「正規化」Unicode 等效性。如果您需要執行此操作,可以使用 ICU(Java 的 Unicode 國際元件)等函式庫。

如果兩個符號具有相同的命名空間和符號名稱,則它們是相等的。在相同條件下,兩個關鍵字是相等的。Clojure 使得關鍵字的相等性測試特別快速(一個簡單的指標比較)。它通過關鍵字類別的 intern 方法實現這一點,該方法保證具有相同命名空間和名稱的所有關鍵字都將傳回相同的關鍵字物件。

數字

如果類型和數值相同,則 Java equals 僅對兩個數字為 true。因此,即使是 Integer 1 和 Long 1,equals 也是 false,因為它們具有不同的類型。例外:如果兩個 BigDecimal 值具有不同的比例,則 Java equals 對於數值相等的兩個 BigDecimal 值也是 false,例如 1.50M 和 1.500M 不相等。此行為已記錄在 BigDecimal 方法 equals 中。

Clojure = 如果「類別」和數字值相同,則為 true。類別之一為

  • 整數或比率,其中整數包含所有 Java 整數類型,例如 ByteShortIntegerLongBigIntegerclojure.lang.BigInt,而比率則以名為 clojure.lang.Ratio 的 Java 類型表示。

  • 浮點數:FloatDouble

  • 十進位數:BigDecimal

因此 (= (int 1) (long 1)) 為 true,因為它們屬於同一個整數類別,但 (= 1 1.0) 為 false,因為它們屬於不同的類別(整數與浮點數)。雖然在 Clojure 實作中,整數和比率是不同的類型,但對於 = 的目的而言,它們實際上屬於同一個類別。比率的算術運算結果如果為整數,會自動轉換為整數。因此,任何類型為 Ratio 的 Clojure 數字都無法等於任何整數,因此在將比率與整數進行比較時,= 始終會提供正確的數字答案(false)。

Clojure 也有 ==,它僅用於比較數字。只要 = 為 true,它就會傳回 true。它也會對數值相等的數字傳回 true,即使它們屬於不同的類別。因此 (= 1 1.0) 為 false,但 (== 1 1.0) 為 true。

您可能會想,為什麼 = 對數字有不同的類別?如果它像 == 一樣運作,要讓 hash= 保持一致將很困難(甚至不可能)(請參閱區段 相等性和雜湊)。想像一下嘗試撰寫 hash,以確保它對 (float 1.5)(double 1.5)、BigDecimal 值 1.50M、1.500M 等和比率 (/ 3 2) 傳回相同的雜湊值。

當 Clojure 的數值用於集合中的元素或映射中的鍵時,Clojure 會使用 = 來比較其相等性。因此,如果您使用具有數字元素的集合或具有數字鍵的映射,Clojure 的數字類別就會發揮作用。

浮點數通常是近似值

請注意,如果您之前不知道浮點數值的近似性質,它們可能會以讓您驚訝的方式運作。它們通常是近似值,僅是因為它們以固定位元數表示,因此許多值無法精確表示,必須近似(或超出範圍)。這適用於任何程式語言中的浮點數。

user> (def d1 (apply + (repeat 100 0.1)))
#'user/d1
user> d1
9.99999999999998
user> (== d1 10.0)
false

有一個名為數值分析的領域,專門研究使用數值近似值的演算法。有許多 Fortran 程式碼的函式庫,因為其浮點運算的順序經過仔細設計,可以保證近似答案和精確答案之間的差異。如果您想要了解相當多的細節,"每個電腦科學家都應該知道的浮點數運算"是一篇不錯的讀物。

如果您想要至少某些類型的問題的精確答案,比率或 BigDecimals 可能符合您的需求。請了解,如果所需的數字位數增加(例如,在許多算術運算之後),這些數字需要變動的記憶體量,並且需要顯著更長的運算時間。如果您想要 pi 或 2 的平方根的精確值,它們也無法提供幫助。

浮點數「非數字」

Clojure 使用底層 Java 雙倍大小的浮點數(64 位元),其表示法和行為由標準 IEEE 754 定義。有一個特殊值NaN(「非數字」),它甚至不等於它本身。Clojure 將此值表示為符號值 ##NaN

user> (Math/sqrt -1)
##NaN
user> (= ##NaN ##NaN)
false
user> (== ##NaN ##NaN)
false

如果這個「值」出現在您的資料中,將會導致一些奇怪的行為。雖然將 ##NaN 加入為集合元素或映射中的金鑰時不會發生錯誤,但您無法搜尋並找到它。您也不能使用 disjdissoc 等函數將其移除。它會正常出現在由包含它的集合建立的序列中。

user> (def s1 #{1.0 2.0 ##NaN})
#'user/s1
user> s1
#{2.0 1.0 ##NaN}
user> (s1 1.0)
1.0
user> (s1 1.5)
nil
user> (s1 ##NaN)
nil             ; cannot find ##NaN in a set, because it is not = to itself

user> (disj s1 2.0)
#{1.0 ##NaN}
user> (disj s1 ##NaN)
#{2.0 1.0 ##NaN}    ; ##NaN is still in the result!

在許多情況下,包含 ##NaN 的集合不會等於(=)另一個集合,即使它們看起來應該相等,因為 (= ##NaN ##NaN)false

user> (= [1 ##NaN] [1 ##NaN])
false

奇怪的是,有一些例外情況,包含 ##NaN 的集合看起來應該相等(=),而且它們確實相等,因為 (identical? ##NaN ##NaN)true

user> (def s2 #{##NaN 2.0 1.0})
#'user/s2
user> s2
#{2.0 1.0 ##NaN}
user> (= s1 s2)
true

Java 在其 equals 方法中有一個特殊情況,適用於浮點數值,這會使 ##NaN 等於它本身。Clojure 的 === 則不會。

user> (.equals ##NaN ##NaN)
true

相等性和雜湊

Java 有 equals 來比較物件對的相等性。

Java 有 hashCode 方法,與此相等性概念一致(或至少有文件說明它應該一致)。這表示對於任何兩個物件 xy,其中 equals 為 true,則 x.hashCode()y.hashCode() 也相等。

此雜湊一致性屬性使得可以使用 hashCode 來實作基於雜湊的資料結構,例如內部使用雜湊技術的映射和集合。例如,雜湊表可以用來實作集合,並且可以保證雜湊值不同的物件可以放入不同的雜湊儲存區,而不同雜湊儲存區中的物件永遠不會彼此相等。

Clojure 有 =hash 出於類似的原因。由於 Clojure = 視為相等的成對事物比 Java equals 多,Clojure hash 必須為更多成對物件傳回相同的雜湊值。例如,無論 = 元素的序列在序列、向量、清單或佇列中,hash 始終傳回相同的值

user> (hash ["a" 5 :c])
1698166287
user> (hash (seq ["a" 5 :c]))
1698166287
user> (hash '("a" 5 :c))
1698166287
user> (hash (conj clojure.lang.PersistentQueue/EMPTY "a" 5 :c))
1698166287

然而,由於在比較 Clojure 不可變集合與其非 Clojure 對應項時,hash= 不一致,混用兩者可能會導致不良行為,如下面的範例所示。

user=> (def java-list (java.util.ArrayList. [1 2 3]))
#'user/java-list
user=> (def clj-vec [1 2 3])
#'user/clj-vec

;; They are =, even though they are different classes
user=> (= java-list clj-vec)
true
user=> (class java-list)
java.util.ArrayList
user=> (class clj-vec)
clojure.lang.PersistentVector

;; Their hash values are different, though.

user=> (hash java-list)
30817
user=> (hash clj-vec)
736442005

;; If java-list and clj-vec are put into collections that do not use
;; their hash values, like a vector or array-map, then those
;; collections will be equal, too.

user=> (= [java-list] [clj-vec])
true
user=> (class {java-list 5})
clojure.lang.PersistentArrayMap
user=> (= {java-list 5} {clj-vec 5})
true
user=> (assoc {} java-list 5 clj-vec 3)
{[1 2 3] 3}

;; However, if java-list and clj-vec are put into collections that do
;; use their hash values, like a hash-set, or a key in a hash-map,
;; then those collections will not be equal because of the different
;; hash values.

user=> (class (hash-map java-list 5))
clojure.lang.PersistentHashMap
user=> (= (hash-map java-list 5) (hash-map clj-vec 5))
false               ; sorry, not true
user=> (= (hash-set java-list) (hash-set clj-vec))
false               ; also not true

user=> (get (hash-map java-list 5) java-list)
5
user=> (get (hash-map java-list 5) clj-vec)
nil                 ; you were probably hoping for 5

user=> (conj #{} java-list clj-vec)
#{[1 2 3] [1 2 3]}          ; you may have been expecting #{[1 2 3]}
user=> (hash-map java-list 5 clj-vec 3)
{[1 2 3] 5, [1 2 3] 3}      ; I bet you wanted {[1 2 3] 3} instead

您在 Clojure 中使用映射時,大多數時候不會指定您要陣列映射還是雜湊映射。預設情況下,如果最多有 8 個鍵,則使用陣列映射,如果超過 8 個鍵,則使用雜湊映射。Clojure 函式會在您對映射進行操作時為您選擇實作。因此,即使您嘗試持續使用陣列映射,在建立較大的映射時,您也可能會經常取得雜湊映射。

我們建議嘗試避免在 Clojure 中使用基於雜湊的集合和映射。它們使用雜湊來幫助在它們的操作中達成高執行效能。相反地,我們建議避免將非 Clojure 集合用作 Clojure 集合中的部分。此建議主要是因為大多數此類非 Clojure 集合都是可變的,而可變性通常會導致微妙的錯誤。另一個原因是 hash= 不一致。

對於實作 java.util.Listjava.util.Setjava.util.Map 的 Java 集合,以及 Clojure 的 hash= 不一致的少數幾種值,也會發生類似的行為。

如果您將雜湊不一致的值用作任何 Clojure 集合中的部分,即使是作為序列集合(例如清單或向量)中的元素,這些集合也會彼此雜湊不一致。這是因為集合的雜湊值是透過結合其部分的雜湊值來計算的。

非 Clojure 集合雜湊不一致的歷史筆記

您可能會想知道為什麼 hash 與非 Clojure 集合的 = 不一致。非 Clojure 集合早在 Clojure 存在之前就已經使用 Java 的 hashCode 方法。當 Clojure 最初開發時,它使用與 hashCode 相同的公式從集合元素計算雜湊函式。

在 Clojure 1.6.0 發布之前,發現 Clojure 的 `hash` 函數中使用 `hashCode` 時,在將小型集合用作設定元素或映射鍵時,可能會導致許多雜湊衝突。

例如,想像一個 Clojure 程式,它使用一個映射來表示一個包含 100 列和 100 行的 2 維網格的內容,其中鍵是範圍 [0, 99] 中的兩個數字的向量。此網格中有 10,000 個此類點,因此映射中有 10,000 個鍵,但 `hashCode` 僅產生 3,169 個不同的結果。

user=> (def grid-keys (for [x (range 100), y (range 100)]
                        [x y]))
#'user/grid-keys
user=> (count grid-keys)
10000
user=> (take 5 grid-keys)
([0 0] [0 1] [0 2] [0 3] [0 4])
user=> (take-last 5 grid-keys)
([99 95] [99 96] [99 97] [99 98] [99 99])
user=> (count (group-by #(.hashCode %) grid-keys))
3169

因此,如果映射使用 Clojure 的雜湊映射的預設實作,則平均每個雜湊儲存區會有 10,000 / 3,169 = 3.16 個衝突。

Clojure 開發人員 分析 了幾個備用的雜湊函數,並選擇了一個基於 Murmur3 雜湊函數的函數,該函數自 Clojure 1.6.0 以來一直在使用。它還使用與 Java 的 `hashCode` 不同的方式來組合集合中多個元素的雜湊。

當時,Clojure 可以將 `hash` 更改為對非 Clojure 集合也使用新技術,但判斷這樣做會顯著減慢一個名為 `hasheq` 的 Java 方法,用於實作 `hash`。請參閱 CLJ-1372 以了解迄今為止已考慮的方法,但到目前為止,還沒有人發現一種競爭性快速的方法來執行此操作。

其他與 `=` 不一致的 `hash` 案例

對於彼此 `=` 的某些 Float 和 Double 值,它們的 `hash` 值是不一致的

user> (= (float 1.0e9) (double 1.0e9))
true
user> (map hash [(float 1.0e9) (double 1.0e9)])
(1315859240 1104006501)
user> (hash-map (float 1.0e9) :float-one (double 1.0e9) :oops)
{1.0E9 :oops, 1.0E9 :float-one}

您可以透過在浮點數碼中持續使用其中一種類型來避免 `Float` 與 `Double` 雜湊不一致。Clojure 將浮點數值預設為雙精度,因此這可能是最方便的選擇。

Rich Hickey 已決定,對於類型 `Float` 和 `Double`,更改雜湊值中的這種不一致性不在 Clojure 的範圍內(在 CLJ-1036 的註解中提到)。已提交 CLJ-1649 錯誤追蹤,建議在將浮點數與雙精度數進行比較時,`=` 始終傳回 false,這將透過消除 `hash` 的限制,使 `hash` 與 `=` 一致,但尚未就此做出決定。

定義您自己的類型的相等性

請參閱以下專案的程式碼,以了解如何執行此操作以及更多資訊。特別是,標準 Java 物件的 Java 方法 `equals` 和 `hashCode`,以及 Clojure Java 方法 `equiv` 和 `hasheq` 與 `=` 和 `hash` 的行為最相關。

參考資料

由 Henry Baker 編寫的論文 "Equal Rights for Functional Objects, or, the More Things Change, The More They Are the Same" 包含以 Common Lisp 編寫的函數 EGAL 程式碼,這是 Clojure 的 = 的靈感來源。對於不變值而言,「深度相等」是有意義的,但對於可變物件而言則不然(除非可變物件在記憶體中是同一個物件),這與程式語言無關。

EGAL 和 Clojure 的 = 之間的一些差異如下所述。這些是關於 EGAL 行為的相當深奧的細節,對於理解 Clojure 的 = 來說並非必要。

將可變集合與其他事物進行比較

將可變物件與任何其他事物進行比較時,EGAL 被定義為 false,除非其他事物是記憶體中相同的可變物件。

為了方便,Clojure 的 = 被設計為在某些情況下將 Clojure 不變集合與非 Clojure 集合進行比較時傳回 true

沒有 Java 方法可以確定任意集合是可變還是不變,因此在 Clojure 中無法實作 EGAL 的預期行為,儘管如果其中一個引數是非 Clojure 集合,則可以考慮 = 「更接近」EGAL

延遲和待處理值

Baker 建議在比較延遲值時,EGAL 強制執行延遲值(請參閱「Equal Rights for Functional Objects」論文中的第 3 節 J。「延遲值」)。將延遲序列與另一個順序事物進行比較時,Clojure 的 = 會強制執行延遲序列的評估,如果到達非 = 序列元素,則停止。分塊序列(例如由 range 產生的序列)可能會導致評估比該點更進一步,就像 Clojure 中任何導致評估延遲序列一部分的事件一樣。

在比較延遲、承諾或未來物件時,Clojure 的 = 不會對其進行 deref。相反,它透過 identical? 進行比較,因此僅在它們是記憶體中的相同物件時才傳回 true,即使對它們呼叫 deref 會產生 = 的值。

封閉

Baker 詳細描述了在某些情況下,EGAL 在將 封閉 與彼此進行比較時,如何傳回 true(請參閱「Equal Rights for Functional Objects」論文中的第 3 節 D。「函數和函數封閉的相等性」)。

當給予函數或閉包作為參數時,Clojure 的 = 僅在它們彼此 identical? 時才會傳回 true

Baker 似乎是因為在某些 Lisp 家族語言中普遍使用閉包來表示物件,而這些物件可能包含可變狀態或不可變值(見下方範例),而定義 EGAL 的動機。由於 Clojure 有多種其他方式來建立不可變值和可變物件(例如記錄、reify、proxy、deftype),因此使用閉包來執行此操作並不常見。

(defn make-point [init-x init-y]
  (let [x init-x
        y init-y]
    (fn [msg]
      (cond (= msg :get-x) x
            (= msg :get-y) y
	    (= msg :get-both) [x y]
	    :else nil))))

user=> (def p1 (make-point 5 7))
#'user/p1
user=> (def p2 (make-point -3 4))
#'user/p2
user=> (p1 :get-x)
5
user=> (p2 :get-both)
[-3 4]
user=> (= p1 p2)
false             ; We expect this to be false,
                  ; because p1 and p2 have different x, y values
user=> (def p3 (make-point 5 7))
#'user/p3
user=> (= p1 p3)
false             ; Baker's EGAL would return true here.  Clojure
                  ; = returns false because p1 and p3 are not identical?

原始作者:Andy Fingerhut