Skip to content

LISP - List Predicates

Predicates are functions that test for a condition and return a boolean value. In Common Lisp, the canonical false value is NIL, and any other value is considered true. List predicates are essential tools for inspecting and validating list structures, forming the basis of safe and robust list-processing code.

Core Structure Predicates: listp, consp, null

Section titled “Core Structure Predicates: listp, consp, null”

These three predicates are fundamental for checking the nature of list-like data. Their distinctions, especially concerning the empty list NIL, are crucial to understand.

PredicateQuestion it AnswersReturns T for…Key Behavior
listp”Is this a proper list?”'(a b c), NIL (the empty list)The most general list checker. (listp nil) is T.
consp”Is this a cons cell?”'(a b c), '(a . b)Checks for a non-empty list structure. (consp nil) is NIL because NIL is not a cons cell.
null”Is this the empty list?”NIL, '()The standard way to check for the empty list NIL. It is the inverse of consp.
(let ((proper-list '(1 2 3))
(dotted-list '(a . b))
(empty-list nil)
(atom 'x))
(format t "~12a | listp: ~a, consp: ~a, null: ~a~%" "Proper List"
(listp proper-list) (consp proper-list) (null proper-list))
(format t "~12a | listp: ~a, consp: ~a, null: ~a~%" "Dotted List"
(listp dotted-list) (consp dotted-list) (null dotted-list))
(format t "~12a | listp: ~a, consp: ~a, null: ~a~%" "Empty List"
(listp empty-list) (consp empty-list) (null empty-list))
(format t "~12a | listp: ~a, consp: ~a, null: ~a~%" "Atom"
(listp atom) (consp atom) (null atom)))
Proper List | listp: T, consp: T, null: NIL
Dotted List | listp: T, consp: T, null: NIL
Empty List | listp: T, consp: NIL, null: T
Atom | listp: NIL, consp: NIL, null: NIL

member checks if an element exists in a list. If found, it returns the sublist starting from the first occurrence of that element (a true value). If not found, it returns NIL.

  • Return Value: Remember, member returns the tail of the list, not just T.
  • Equality Test: By default, member uses #'eql. You can specify a different test with the :test keyword, such as #'equal for comparing structures.
(let ((my-list '(a b c b d)))
(format t "Is 'b in the list? ~a~%" (member 'b my-list)) ; -> (B C B D)
(format t "Is 'z in the list? ~a~%" (member 'z my-list))) ; -> NIL
(let ((nested-list '((x 1) (y 2) (z 3))))
;; #'eql (default) fails to find a structurally similar list
(format t "Find (y 2) with #'eql? ~a~%" (member '(y 2) nested-list))
;; #'equal succeeds
(format t "Find (y 2) with #'equal? ~a~%" (member '(y 2) nested-list :test #'equal)))
Is 'b in the list? (B C B D)
Is 'z in the list? NIL
Find (y 2) with #'eql? NIL
Find (y 2) with #'equal? ((Y 2) (Z 3))

assoc - Finding a Key in an Association List

Section titled “assoc - Finding a Key in an Association List”

assoc (association) is used to search an association list (alist), which is a list of key-value pairs. If the key is found, it returns the entire key-value pair. Otherwise, it returns NIL.

(let ((config '((:user . "admin") (:port . 8080) (:debug . t))))
(format t "Port setting: ~a~%" (assoc :port config)) ; -> (:PORT . 8080)
(format t "Host setting: ~a~%" (assoc :host config)) ; -> NIL
;; To get just the value, combine with `cdr`
(let ((port-pair (assoc :port config)))
(if port-pair
(format t "Port value: ~a~%" (cdr port-pair)) ; -> 8080
(format t "Port not found.~%"))))
Port setting: (:PORT . 8080)
Host setting: NIL
Port value: 8080

endp is a generalized predicate for checking if a list has reached its end. It returns T if its argument is NIL, and NIL otherwise. Its primary advantage over null is that it signals an error if given a non-list argument, making it a safer choice for terminating recursive functions or loops that expect lists.

(defun count-elements (lst)
"Safely counts the elements in a list using endp."
(if (endp lst)
0
(1+ (count-elements (rest lst)))))
(format t "Count for '(a b c): ~a~%" (count-elements '(a b c))) ; -> 3
(format t "Count for nil: ~a~%" (count-elements nil)) ; -> 0
;; (count-elements 'not-a-list) ; This would correctly signal an error.
Count for '(a b c): 3
Count for nil: 0