LISP - List Predicates
Modern Lisp: List Predicates
Section titled “Modern 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.
| Predicate | Question it Answers | Returns 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. |
Example: Comparing Structure Predicates
Section titled “Example: Comparing Structure Predicates”(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)))Output
Section titled “Output”Proper List | listp: T, consp: T, null: NILDotted List | listp: T, consp: T, null: NILEmpty List | listp: T, consp: NIL, null: TAtom | listp: NIL, consp: NIL, null: NILElement and Association Predicates
Section titled “Element and Association Predicates”member - Checking for Element Presence
Section titled “member - Checking for Element Presence”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,
memberreturns the tail of the list, not justT. - Equality Test: By default,
memberuses#'eql. You can specify a different test with the:testkeyword, such as#'equalfor 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)))Output
Section titled “Output”Is 'b in the list? (B C B D)Is 'z in the list? NILFind (y 2) with #'eql? NILFind (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.~%"))))Output
Section titled “Output”Port setting: (:PORT . 8080)Host setting: NILPort value: 8080Loop Termination: endp
Section titled “Loop Termination: endp”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.
Example: Safe Recursive Function
Section titled “Example: Safe Recursive Function”(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.Output
Section titled “Output”Count for '(a b c): 3Count for nil: 0