Skip to content

LISP - Predicates

In Common Lisp, a predicate is a function that tests for a certain condition. It returns a true or false value. A key concept in Lisp is its representation of truth: the symbol nil is the only false value, while any other value is considered true. By convention, the symbol t is used as the canonical true value.

Predicates are conventionally named with a -p or p suffix (the ‘p’ stands for ‘predicate’), like numberp or evenp.

Here are some of the most frequently used predicates, grouped by their purpose.

Choosing the right equality predicate is a common task in Lisp. They differ in how strictly they compare their arguments.

PredicateDescription
eqReturns t if the arguments are the exact same object in memory. It’s the strictest and fastest comparison.
eqlLike eq, but also returns t for identical numbers and characters. For example, (eql 5 5) is t, but (eql 5 5.0) is nil.
equalReturns t if the arguments are structurally similar. It recursively compares lists and strings. For example, two different lists with the same elements are equal.
equalpThe most lenient comparison. Like equal, but ignores case in strings and compares numbers of different types (e.g., (equalp 5 5.0) is t).
PredicateDescription
atomReturns t if the argument is an atom (i.e., not a cons cell/list).
listpReturns t if the argument is a list (a cons cell or nil).
numberpReturns t if the argument is any kind of number.
integerpReturns t if the argument is an integer.
floatpReturns t if the argument is a floating-point number.
stringpReturns t if the argument is a string.
symbolpReturns t if the argument is a symbol.
nullReturns t if the argument is nil, and nil otherwise.
PredicateDescription
zeropTakes one numeric argument and returns t if it is zero.
evenpTakes one integer argument and returns t if it is even.
oddpTakes one integer argument and returns t if it is odd.
>Takes one or more numbers. Returns t if they are in strictly decreasing order.
<Takes one or more numbers. Returns t if they are in strictly increasing order.
=Takes one or more numbers. Returns t if they are all numerically equal.

Let’s create a source file main.lisp to demonstrate these concepts. This example highlights the differences between equality predicates and shows some common type checks.

;;; main.lisp - Demonstrating Common Lisp Predicates
(defun run-predicate-demos ()
"Runs a series of demonstrations for predicate functions."
(format t "--- Equality Predicate Demos ---~%")
(let ((list1 (list 'a 'b))
(list2 (list 'a 'b)))
(format t "(eq list1 list2) -> ~a (Different objects in memory)~%" (eq list1 list2))
(format t "(equal list1 list2) -> ~a (Structurally equivalent)~%~%" (equal list1 list2)))
(format t "--- Numeric Predicate Demos ---~%")
(format t "(oddp 7) -> ~a~%" (oddp 7))
(format t "(evenp 10) -> ~a~%" (evenp 10))
(format t "(zerop 0.0) -> ~a~%" (zerop 0.0))
(format t "(= 5 5.0) -> ~a (Numeric equality)~%" (= 5 5.0))
(format t "(> 10 5 1) -> ~a~%" (> 10 5 1))
(format t "(< 2 4 8) -> ~a~%~%" (< 2 4 8))
(format t "--- Type Predicate Demos ---~%")
(format t "(listp '(1 2 3)) -> ~a~%" (listp '(1 2 3)))
(format t "(atom 'my-symbol) -> ~a~%" (atom 'my-symbol))
(format t "(stringp \"Hello\") -> ~a~%" (stringp "Hello"))
(format t "(null nil) -> ~a~%" (null nil)))
;; To run this code, load the file in a Lisp environment and call (run-predicate-demos)
(run-predicate-demos)

When you load and execute this code in a Common Lisp implementation (like SBCL), you will see:

--- Equality Predicate Demos ---
(eq list1 list2) -> NIL (Different objects in memory)
(equal list1 list2) -> T (Structurally equivalent)
--- Numeric Predicate Demos ---
(oddp 7) -> T
(evenp 10) -> T
(zerop 0.0) -> T
(= 5 5.0) -> T (Numeric equality)
(> 10 5 1) -> T
(< 2 4 8) -> T
--- Type Predicate Demos ---
(listp '(1 2 3)) -> T
(atom 'my-symbol) -> T
(stringp "Hello") -> T
(null nil) -> T

Practical Application: Factorial with zerop

Section titled “Practical Application: Factorial with zerop”

Predicates are fundamental for controlling program flow. Here is a classic recursive factorial function that uses the zerop predicate as its base case.

;;; factorial.lisp - A recursive factorial implementation
(defun factorial (n)
"Computes the factorial of a non-negative integer N."
(check-type n (integer 0 *) "N must be a non-negative integer.")
(if (zerop n)
1 ; Base case: 0! = 1
(* n (factorial (1- n))))) ; Recursive step: n * (n-1)!
;; Example usage:
(let ((num 6))
(format t "Factorial of ~d is: ~d~%" num (factorial num)))

Executing this code produces:

Factorial of 6 is: 720
  • Choosing Equality: Use eq for symbol comparison, eql as a general default, equal for comparing lists or sequences, and equalp when you need flexible, case-insensitive, or type-insensitive comparisons.
  • Numeric Comparison: Always use = for comparing numbers, not eql or equal, as = correctly handles different numeric types (e.g., integers and floats).
  • Error Checking: Use predicates for robust error checking. The check-type macro in the factorial example is a powerful way to enforce that arguments have the correct type.