Skip to content

LISP - 相等谓词

在许多语言中,检查两个事物是否相等是一个简单的 == 操作。在 Lisp 中,“相等”的概念更为细致和强大。Common Lisp 提供了一套相等谓词,每个谓词都有精确的含义,允许你选择所需的确切比较类型。理解它们之间的区别是编写正确且无 bug 的 Lisp 代码的关键。

主要谓词形成一个严格程度的层次结构,从最具体到最一般:

eq (most strict) -> eql -> equal -> equalp (most general)
谓词检查内容类比
eq同一性 - 它们是内存中的同一个对象吗?同一个人。
eql原语值 - 它是否 eq,或者是类型和值都相同的数字,或是相同的字符?一个人的社会安全号码。
equal结构等价性 - 它们具有相同的结构和内容吗?同卵双胞胎。
equalp宽容等价性 - 它们“看起来”一样吗,忽略大小写和数字类型?一个人和他们的带照片的身份证。

eq 仅当其参数是内存中的同一个对象时才返回 T。它速度快但用途有限。

;; 具有相同名称的符号始终是同一个对象。
(eq 'hello 'hello) ; => T
;; 两个内容相同的列表不是同一个对象。
(eq (list 'a 'b) (list 'a 'b)) ; => NIL
;; 但指向列表的变量与列表是 `eq` 的。
(let ((x (list 'a 'b)))
(eq x x)) ; => T
;; 对于数字,`eq` 是实现相关的,不应依赖。
(eq 1 1) ; => 可能是 T 或 NIL。请改用 `eql` 或 `=`。

eql 类似于 eq,但它也能可靠地按值比较数字(同类型)和字符。它是 case 默认使用的谓词。

(eql 'hello 'hello) ; => T (与符号的 eq 相同)
(eql 10 10) ; => T (数字可靠)
(eql 10 10.0) ; => NIL (类型不同)
(eql #\a #\a) ; => T (字符)
(eql #\a #\A) ; => NIL (区分大小写)

equal 递归地比较列表、向量和字符串的内容。如果它们具有相同的结构和元素,则返回 T。

;; 两个内容相同的列表是 `equal` 的。
(equal (list 'a 'b) (list 'a 'b)) ; => T
;; 内容相同的字符串是 `equal` 的。
(equal "hello" "hello") ; => T
;; 数字使用 `eql` 比较。
(equal 10 10.0) ; => NIL

equalp 类似于 equal,但更加宽松。它适用于比较来自外部源或用户输入的数据,这些数据可能在类型和大小写上不一致。

  • 它按值比较数字,忽略类型(10 和 10.0 是 equalp 的)。
  • 它以不区分大小写的方式比较字符和字符串。
;; 忽略数字类型。
(equalp 10 10.0) ; => T
;; 字符串比较不区分大小写。
(equalp "hello" "Hello") ; => T
;; 字符比较不区分大小写。
(equalp #\a #\A) ; => T
  • 对于数字,为了清晰起见,请使用数字专用的谓词:=、/= 、<、>。(eql 10 10.0) 为假,但 (= 10 10.0) 为真。
  • 对于符号,eq 是最快且最符合习惯的选择。
  • 对于字符串,优先使用专用函数 string= (区分大小写) 和 string-equal (不区分大小写)。它们能更明确地表达你的意图。
  • 对于结构化数据(如列表或树),如果你需要精确的结构匹配,equal 是你的工具。
  • 对于用户输入或模糊匹配,当你需要忽略大小写或数字类型差异时,equalp 是合适的。
  • 当有疑问时,eql 是非结构化数据通用比较的安全默认选项。