Skip to content

LISP - Equality Predicates

In many languages, checking if two things are equal is a simple == operation. In Lisp, the concept of ‘equality’ is more nuanced and powerful. Common Lisp provides a suite of equality predicates, each with a precise meaning, allowing you to choose the exact type of comparison you need. Understanding the difference between them is key to writing correct and bug-free Lisp code.

The main predicates form a hierarchy of strictness, from most specific to most general:

eq (most strict) -> eql -> equal -> equalp (most general)
PredicateChecks ForAnalogy
eqIdentity - Are these the exact same object in memory?The very same person.
eqlValue of Primitives - Is it eq, or are they numbers of the same type and value, or the same character?A person’s social security number.
equalStructural Equivalence - Do they have the same structure and contents?Identical twins.
equalpPermissive Equivalence - Do they ‘look’ the same, ignoring case and numeric types?A person and their photo ID.

eq returns T only if its arguments are the very same object in memory. It’s fast but has limited use.

;; Symbols with the same name are always the same object.
(eq 'hello 'hello) ; => T
;; Two lists with the same contents are NOT the same object.
(eq (list 'a 'b) (list 'a 'b)) ; => NIL
;; But a variable pointing to a list is `eq` to the list.
(let ((x (list 'a 'b)))
(eq x x)) ; => T
;; For numbers, `eq` is implementation-dependent and should not be relied upon.
(eq 1 1) ; => Might be T or NIL. Use `eql` or `=` instead.

eql is like eq, but it also reliably compares numbers (of the same type) and characters by value. It’s the default predicate used by case.

(eql 'hello 'hello) ; => T (like eq for symbols)
(eql 10 10) ; => T (reliable for numbers)
(eql 10 10.0) ; => NIL (types are different)
(eql #\a #\a) ; => T (characters)
(eql #\a #\A) ; => NIL (case-sensitive)

equal recursively compares the contents of lists, vectors, and strings. It returns T if they have the same structure and elements.

;; Two lists with same contents are `equal`.
(equal (list 'a 'b) (list 'a 'b)) ; => T
;; Strings with same contents are `equal`.
(equal "hello" "hello") ; => T
;; Numbers are compared using `eql`.
(equal 10 10.0) ; => NIL

equalp is like equal but even more lenient. It’s useful for comparing data from external sources or user input where types and case may be inconsistent.

  • It compares numbers by value, ignoring type (10 and 10.0 are equalp).
  • It compares characters and strings in a case-insensitive way.
;; Numeric types are ignored.
(equalp 10 10.0) ; => T
;; String comparison is case-insensitive.
(equalp "hello" "Hello") ; => T
;; Character comparison is case-insensitive.
(equalp #\a #\A) ; => T
  • For numbers, use the numeric-specific predicates for clarity: =, /=, <, >. (eql 10 10.0) is false, but (= 10 10.0) is true.
  • For symbols, eq is the fastest and most idiomatic choice.
  • For strings, prefer the dedicated functions string= (case-sensitive) and string-equal (case-insensitive). They are more explicit about your intent.
  • For structured data (like lists or trees), equal is your tool if you need an exact structural match.
  • For user input or fuzzy matching, equalp is appropriate when you need to ignore differences in case or numeric types.
  • When in doubt, eql is a safe default for general-purpose comparison of non-structured data.