Common Lisp 和其他语言一样,有一套决定两个对象是否“相同”的规则。这些规则定义了四个层级,每个层级都有一个执行该级别检查的函数。这些层级按从最严格到最宽松的顺序排列。
eq第一层是对象同一性。这种相等性用函数eq检查。被检查相等性的两个对象必须是同一个对象:
(eq 'apples 'apples) ; => T
(eq 'apples 'oranges) ; => NIL
(eq '(a b c) '(a b c) ; => NIL (these two lists have the same contents but are not the same list)
(let ((list1 '(a b c)) (list2 list1))
(eq list1 list2)) ; => T (these two lists are the same list)
eql第二层加入了数字和字符的相等性。这种相等性用函数eql检查。检查的方式取决于实参的类型:
eq的对象也都是eql的eql的eql的。(eql 1 1) ; => T
(eql 1 1/1) ; => NIL (one number is an integer, the other a rational)
(eql #\c #\c) ; => T
(eql #\c #\C) ; => NIL (case is different)
你可能会奇怪,为什么数字和字符不用eq比较对象同一性。Common Lisp 标准允许实现自行选择是否复制数字和字符。因此0和0可能不是eq的,因为它们可能是数字0的不同实例。
equal第三层检查结构上的相似性。这种相等性用equal检查。检查的方式取决于实参的类型:
eq的方式比较eql的方式比较equal的,那么这些 cons 就是equal的。这一比较是递归进行的。eql的,那么它们就是equal的eq的方式比较equal的。(在构成路径名各组成部分的字符串的大小写敏感性方面,这里为依赖具体实现的行为留有余地。)eq的方式比较(equal '(a (b c)) '(a (b c))) ; => T (conses are equal if their contents are equal)
(equal "hello" "hello") ; => T
(equal "hello" "HELLO") ; => NIL
(equal #(1 2 3) #(1 2 3)) ; => NIL (arrays are equal only if eq)
(equal #P"foo/bar.md" #P"foo/bar.md") ; => T (pathnames are equal if "functionally equivalent"
equalp第四层,也是最宽松的一层相等性,用equalp检查。检查的方式取决于类型:
equalp的,那么它们就是equalp的equalp的equalp的,那么这些 cons 就是equalp的。这一比较是递归进行的。equalp的,那么这些数组就是equalp的。equalp的,那么这些结构体就是equalp的。:test函数相同、键相同(按该:test函数比较),并且这些键对应的值按equalp比较也相同,那么这两个哈希表就是equalp的。(equalp 1 1.0) ; => T
(equalp #\c #\C) ; => T
(equalp "hello" "HELLO") ; => T
(equalp #(1 2 3) #(1.0 2.0 3.0)) ; => T (arrays contain elements which are `equalp`)
(equal #S(TEST :SLOT1 'a :SLOT2 'b)
#S(TEST :SLOT1 'a :SLOT2 'b)) ; => T (structures of the same class with slots that have values which are `equalp`)
以上都是“通用”的相等性函数。按定义,它们适用于任何类型。当你编写通用代码、直到运行时才知道要比较的对象的类型时,这会很有用。不过,当你知道被比较的类型时,一般认为使用类型专属的相等性函数是“更好的风格”。例如用string=而不是equal。这些函数会在相关概念中介绍和讨论。