Skip to content

LISP - `and` 方法组合

Common Lisp 对象系统(CLOS)是 Lisp 最强大的特性之一。它提供了一种灵活的面向对象范式,与 Java 或 C++ 等系统有所不同。CLOS 中的一个关键概念是泛型函数(Generic Function),它根据其参数的类型(或类)分派到特定的方法(Method)。

方法组合(Method Combination)是定义给定泛型函数调用所有适用方法的执行结果如何组合以产生单个最终值的机制。默认行为是标准方法组合(Standard Method Combination),但 Lisp 还提供了其他几种内置组合类型,例如 and。

and 方法组合适用于所有独立检查都必须通过的验证场景。它的工作方式如下:

  • 它会逐一调用所有适用的主方法,从最具体到最不具体。
  • 如果任何方法返回 nil,它会立即停止并返回 nil。
  • 如果所有方法都被调用且没有一个返回 nil,它会返回最后被调用的方法(最不具体的那个)的非 nil 值。

想象一下,我们需要验证传入的数据请求。一个请求只有在其所有组件都有效时才有效。这是 and 方法组合的完美用例。

;; main.lisp
;; 1. 定义一个使用 'and' 方法组合的泛型函数。
;; 这个函数将检查一个对象是否有效。
(defgeneric is-valid-p (object)
(:documentation "检查对象是否有效。所有检查都必须通过。")
(:method-combination and))
;; 2. 为不同的类型定义方法。每个方法执行一个特定的检查。
;; 最通用的检查:默认情况下,除非另有指定,否则任何事物都是有效的。
;; 如果所有其他检查都通过,此方法将最后被调用并提供最终的 't'。
(defmethod is-valid-p and (object)
(declare (ignore object))
t) ; 默认为真
;; 数字的检查:必须是正数。
(defmethod is-valid-p and ((n number))
(> n 0))
;; 字符串的检查:不能为空。
(defmethod is-valid-p and ((s string))
(> (length s) 0))
;; 3. 使用不同的输入测试泛型函数。
;; --- 测试用例 ---
;; 正数是有效的。
(format t "Is 10 valid? ~a~%" (is-valid-p 10)) ; -> T
;; 负数无效。数字方法返回 nil,短路操作发生。
(format t "Is -5 valid? ~a~%" (is-valid-p -5)) ; -> NIL
;; 非空字符串是有效的。
(format t "Is \"hello\" valid? ~a~%" (is-valid-p "hello")) ; -> T
;; 空字符串无效。
(format t "Is \"\" valid? ~a~%" (is-valid-p "")) ; -> NIL
;; 列表是有效的,因为它会通过最通用的 'object' 方法。
(format t "Is '(1 2) valid? ~a~%" (is-valid-p '(1 2))) ; -> T
Is 10 valid? T
Is -5 valid? NIL
Is "hello" valid? T
Is "" valid? NIL
Is '(1 2) valid? T

在此示例中:

  • 我们将 is-valid-p 定义为一个使用 and 组合的泛型函数。
  • 当我们调用 (is-valid-p 10) 时,CLOS 找到两个适用的方法:一个专门用于 number,另一个用于 object。number 方法更具体,因此它首先运行并返回 t。
  • 由于它没有返回 nil,and 组合会继续执行下一个方法,即 object 的方法,该方法也返回 t。这是最后一个值,因此最终结果是 t。
  • 当我们调用 (is-valid-p -5) 时,number 方法运行并返回 nil。and 组合会立即停止并返回 nil,而不会调用 object 方法。

Common Lisp 提供了其他有用的短形式方法组合,例如 or(返回第一个非 nil 值)、+(对结果求和)、append(追加列表结果)、min 和 max。

对于更复杂的场景,你可以使用标准方法组合,它允许你定义主方法以及使用 :before、:after 和 :around 等限定符的辅助方法。这让你对方法执行有精细的控制,是高级 Lisp 编程的基石。