LISP - Custom Predicates
Modern Lisp: Defining Custom Predicates
Section titled “Modern Lisp: Defining Custom Predicates”A predicate is a function that answers a true/false question about its input. In Common Lisp, predicates are fundamental. They return T (for true) or NIL (for false). Defining your own clear, robust predicates is key to writing expressive and readable Lisp code.
The Basics of Defining a Predicate
Section titled “The Basics of Defining a Predicate”You define a predicate just like any other function, using defun. By convention, the names of predicate functions end with a -P.
(defun <predicate-name>-p (parameter-list) "Optional documentation string." (body-that-evaluates-to-t-or-nil))Example 1: Checking for a Positive Number
Section titled “Example 1: Checking for a Positive Number”A simple predicate that checks if a number is greater than zero.
(defun positive-p (number) "Returns T if NUMBER is a number greater than zero, otherwise NIL." (and (numberp number) (> number 0)))
;; --- Tests ---(print (positive-p 10)) ;=> T(print (positive-p -5)) ;=> NIL(print (positive-p 0)) ;=> NIL(print (positive-p "abc")) ;=> NIL (due to the `numberp` check)Note: We added (numberp number) to make the function more robust. It now handles non-numeric input gracefully instead of causing an error.
Example 2: Checking for an Empty List
Section titled “Example 2: Checking for an Empty List”While Lisp has the built-in null function for this, defining it yourself is a good exercise.
(defun empty-list-p (list) "Returns T if LIST is the empty list '(), otherwise NIL." (null list))
;; --- Tests ---(print (empty-list-p '())) ;=> T(print (empty-list-p nil)) ;=> T (NIL is equivalent to '())(print (empty-list-p '(a b))) ;=> NILExample 3: Checking if a Number is Within a Range
Section titled “Example 3: Checking if a Number is Within a Range”Common Lisp’s comparison operators like < and <= can take multiple arguments, which leads to very clean and readable code for range checks.
(defun in-range-p (num lower upper) "Returns T if NUM is between LOWER and UPPER (inclusive)." (and (numberp num) (numberp lower) (numberp upper) (<= lower num upper)))
;; --- Tests ---(print (in-range-p 5 1 10)) ;=> T(print (in-range-p 10 1 10));=> T(print (in-range-p 0 1 10)) ;=> NILEnhancing Predicates with Type Checking
Section titled “Enhancing Predicates with Type Checking”For writing truly robust code, especially for libraries or team projects, it’s better to signal an error on invalid input rather than returning NIL. The check-type macro is perfect for this.
(defun positive-p (number) "Returns T if NUMBER is a number greater than zero. Signals a type-error if NUMBER is not a number." (check-type number number) (> number 0))
;; (positive-p 10) ;=> T;; (positive-p "abc") ;=> Signals a correctable TYPE-ERRORTesting Your Predicates
Section titled “Testing Your Predicates”It’s a good practice to write tests for your functions. The assert macro is a simple way to create a basic test suite. It signals an error if its argument expression evaluates to NIL.
(defun test-predicates () "Runs a simple test suite for our custom predicates." ;; Test positive-p (assert (positive-p 5)) (assert (not (positive-p -5))) (assert (not (positive-p 0)))
;; Test in-range-p (assert (in-range-p 5 0 10)) (assert (not (in-range-p 11 0 10)))
(format t "All tests passed!"))
;; To run the tests:;; (test-predicates)Best Practices for Writing Predicates
Section titled “Best Practices for Writing Predicates”- Naming Convention: Always use the
-psuffix. It makes code self-documenting (e.g.,(if (user-active-p user) ...)). - Documentation: Write a clear docstring explaining what the predicate checks, its parameters, and what it returns.
- Be Explicit: Ensure your function always returns either
TorNIL. Use(not ...)to invert a condition rather than relying on other “false” values. - Leverage Built-ins: Before writing a custom predicate, check if a built-in Lisp function already does what you need (e.g.,
evenp,oddp,streamp,listp,null). - Consider Input Types: Decide whether your predicate should return
NILfor invalid types (flexible) or signal an error withcheck-type(robust).