LISP - Equality Predicates
Lisp - A Guide to Equality Predicates
Section titled “Lisp - A Guide to 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)The Four Main Predicates
Section titled “The Four Main Predicates”| Predicate | Checks For | Analogy |
|---|---|---|
eq | Identity - Are these the exact same object in memory? | The very same person. |
eql | Value 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. |
equal | Structural Equivalence - Do they have the same structure and contents? | Identical twins. |
equalp | Permissive Equivalence - Do they ‘look’ the same, ignoring case and numeric types? | A person and their photo ID. |
Detailed Examples
Section titled “Detailed Examples”eq - The Strictest Comparison
Section titled “eq - The Strictest Comparison”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 - The Go-To for Primitives
Section titled “eql - The Go-To for Primitives”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 - For Comparing Structures
Section titled “equal - For Comparing Structures”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) ; => NILequalp - The Most Permissive Comparison
Section titled “equalp - The Most Permissive Comparison”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 (
10and10.0areequalp). - 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) ; => THow to Choose: Best Practices
Section titled “How to Choose: Best Practices”- For numbers, use the numeric-specific predicates for clarity:
=,/=,<,>.(eql 10 10.0)is false, but(= 10 10.0)is true. - For symbols,
eqis the fastest and most idiomatic choice. - For strings, prefer the dedicated functions
string=(case-sensitive) andstring-equal(case-insensitive). They are more explicit about your intent. - For structured data (like lists or trees),
equalis your tool if you need an exact structural match. - For user input or fuzzy matching,
equalpis appropriate when you need to ignore differences in case or numeric types. - When in doubt,
eqlis a safe default for general-purpose comparison of non-structured data.