Skip to content

LISP - Comparison Predicates

In Lisp, a predicate is a function that tests for a condition and returns a boolean value. Unlike many other languages, Lisp’s canonical boolean values are the symbol t for true and nil for false. These predicates are fundamental for controlling program flow in constructs like if, cond, when, and unless.

This tutorial covers two main categories of predicates: those for numeric comparison and those for checking equality between any Lisp objects.

These functions compare numeric values. A powerful feature of Common Lisp is that they can accept more than two arguments and perform the comparison sequentially.

PredicateDescription
<Returns t if the numbers are in monotonically increasing order. (< 1 3 5) is t.
>Returns t if the numbers are in monotonically decreasing order. (> 5 3 1) is t.
<=Returns t if the numbers are in monotonically non-decreasing order. (<= 3 3 5) is t.
>=Returns t if the numbers are in monotonically non-increasing order. (>= 5 5 3) is t.
=Returns t if all numeric arguments are equal in value. (= 5 5.0) is t.
/=Returns t if no two numeric arguments are equal in value. (/= 1 2 3) is t.

Let’s see these functions in action in a modern Common Lisp environment like SBCL (Steel Bank Common Lisp).

;; main.lisp
;; Basic two-argument comparison
(print (< 3 5)) ; T
(print (> 3 5)) ; NIL
;; Multi-argument comparison
(print (< 10 20 30)) ; T
(print (>= 50 25 25 1)) ; T
;; Equality checks for numbers
(print (= 10 10 10)) ; T
(print (= 10 10.0)) ; T (types are different, but values are the same)
;; Inequality check
(print (/= 5 3 5)) ; NIL (because the first and third are equal)
(print (/= 5 4 3)) ; T
T
NIL
T
T
T
T
NIL
T

Choosing the right equality predicate is a common task in Lisp and crucial for correctness. They differ in how strictly they compare objects.

PredicateDescription & Use Case
eqIdentity: Returns t if arguments are the same object in memory. Very fast. Use for comparing symbols or checking if two variables point to the exact same object.
eqlIdentity for non-numbers, value for numbers: Like eq, but also returns t if arguments are numbers of the same type and value. This is the default predicate for case.
equalStructural Equality: Recursively compares the contents of lists, strings, and other structures. Returns t if they have a similar structure and equivalent contents. Slower but more intuitive for structured data.
equalpPermissive Equality: Like equal, but even more relaxed. It ignores case in strings/characters and numeric type differences (e.g., (equalp 5 5.0) is t).

Example: Choosing the Right Equality Predicate

Section titled “Example: Choosing the Right Equality Predicate”
;; main.lisp
(let ((list-a (list 'x 'y))
(list-b (list 'x 'y)))
;; EQ checks for the same object in memory
(print (eq 'hello 'hello)) ; T (symbols are unique)
(print (eq list-a list-b)) ; NIL (they are two different list objects)
(print (eq list-a list-a)) ; T (it's the same object)
(terpri) ; Newline for clarity
;; EQL is like EQ but works for numbers of the same type
(print (eql 10 10)) ; T
(print (eql 10 10.0)) ; NIL (different types)
(terpri)
;; EQUAL checks for structural similarity
(print (equal "hello" "hello")) ; T
(print (equal list-a list-b)) ; T (contents are equivalent)
(terpri)
;; EQUALP is permissive
(print (equalp "hello" "Hello")) ; T (case-insensitive)
(print (equalp 10 10.0)) ; T (numeric types ignored)
)
T
NIL
T
T
NIL
T
T
T
T
  • Default to eql: When in doubt, start with eql. It’s a safe and efficient default.
  • Use = for numbers: Always use = for numeric equality, as it correctly handles different numeric types (e.g., integer vs float).
  • Use equal for lists and strings: When you need to compare the contents of data structures, equal is your tool.
  • Avoid eq for numbers and lists: Using eq on numbers or newly created lists can lead to unexpected behavior, as the compiler is not required to reuse the same memory object.
  • Further Reading: For a deep dive, explore the Common Lisp Hyperspec, the official standard for the language.