Skip to content

LISP - 自定义谓词

一个谓词(predicate)是一个函数,它回答关于其输入的一个真/假问题。在 Common Lisp 中,谓词是基础性的。它们返回 T(表示真)或 NIL(表示假)。定义你自己的清晰、健壮的谓词是编写富有表达力且可读性强的 Lisp 代码的关键。

你像定义其他任何函数一样定义谓词,使用 defun。按照惯例,谓词函数的名称以 -P 结尾。

(defun <predicate-name>-p (parameter-list)
"可选的文档字符串。"
(计算结果为 T 或 NIL 的函数体))

一个简单的谓词,检查数字是否大于零。

(defun positive-p (number)
"如果 NUMBER 是一个大于零的数字,则返回 T;否则返回 NIL。"
(and (numberp number) (> number 0)))
;; --- 测试 ---
(print (positive-p 10)) ;=> T
(print (positive-p -5)) ;=> NIL
(print (positive-p 0)) ;=> NIL
(print (positive-p "abc")) ;=> NIL (因为进行了 `numberp` 检查)

注意:我们添加了 (numberp number) 以使函数更健壮。现在它能优雅地处理非数字输入,而不是引发错误。

虽然 Lisp 有内置的 null 函数用于此目的,但自己定义它是一个很好的练习。

(defun empty-list-p (list)
"如果 LIST 是空列表 '(),则返回 T;否则返回 NIL。"
(null list))
;; --- 测试 ---
(print (empty-list-p '())) ;=> T
(print (empty-list-p nil)) ;=> T (NIL 等同于 '())
(print (empty-list-p '(a b))) ;=> NIL

Common Lisp 的比较运算符如 < 和 <= 可以接受多个参数,这使得范围检查的代码非常清晰和可读。

(defun in-range-p (num lower upper)
"如果 NUM 介于 LOWER 和 UPPER 之间(包含边界),则返回 T。"
(and (numberp num) (numberp lower) (numberp upper)
(<= lower num upper)))
;; --- 测试 ---
(print (in-range-p 5 1 10)) ;=> T
(print (in-range-p 10 1 10));=> T
(print (in-range-p 0 1 10)) ;=> NIL

为了编写真正健壮的代码,特别是对于库或团队项目,最好在无效输入时发出错误信号,而不是返回 NIL。check-type 宏非常适合此目的。

(defun positive-p (number)
"如果 NUMBER 是一个大于零的数字,则返回 T。
如果 NUMBER 不是数字,则发出类型错误信号。"
(check-type number number)
(> number 0))
;; (positive-p 10) ;=> T
;; (positive-p "abc") ;=> 发出一个可纠正的 TYPE-ERROR

为你的函数编写测试是一个好习惯。assert 宏是创建基本测试套件的简单方法。如果其参数表达式计算结果为 NIL,它将发出错误信号。

(defun test-predicates ()
"为我们的自定义谓词运行一个简单的测试套件。"
;; 测试 positive-p
(assert (positive-p 5))
(assert (not (positive-p -5)))
(assert (not (positive-p 0)))
;; 测试 in-range-p
(assert (in-range-p 5 0 10))
(assert (not (in-range-p 11 0 10)))
(format t "所有测试通过!"))
;; 要运行测试:
;; (test-predicates)
  • 命名约定:始终使用 -p 后缀。这使得代码具有自文档性(例如,(if (user-active-p user) ...))。
  • 文档:编写清晰的文档字符串,解释谓词检查什么、其参数以及它返回什么。
  • 明确性:确保你的函数始终返回 T 或 NIL。使用 (not ...) 来反转条件,而不是依赖其他“假”值。
  • 利用内置函数:在编写自定义谓词之前,检查是否有内置的 Lisp 函数已经实现了你需要的功能(例如 evenp、oddp、streamp、listp、null)。
  • 考虑输入类型:决定你的谓词是应该对无效类型返回 NIL(灵活),还是使用 check-type 发出错误信号(健壮)。