Skip to content

LISP - `+` 方法组合

Common Lisp 对象系统 (CLOS) 提供了一个强大而独特的功能,称为方法组合(Method Combination)。它允许您定义多个适用于同一泛型函数调用的方法如何组合以产生最终结果。本教程将探讨 + 方法组合,这是一种内置的、用于汇总适用方法结果的技术,非常适合聚合和评分等任务。

核心概念:使用 + 组合定义泛型函数

Section titled “核心概念:使用 + 组合定义泛型函数”

要使用 + 方法组合,您需要在使用 defgeneric 定义泛型函数时指定它。为此泛型函数定义的所有主方法都应返回一个数字。CLOS 将自动对这些数字求和以产生最终结果。

(defgeneric calculate-cost (x y)
(:documentation "通过汇总专门方法的结果来计算总成本。")
(:method-combination + :most-specific-first))
;; 为两个数字定义一个主方法。这是要组合的方法之一。
(defmethod calculate-cost ((x number) (y number))
"基本成本是两个数字之和。"
(+ x y))
  • defgeneric:这是声明组合策略的地方。(defgeneric calculate-cost (x y) ...) 定义了函数的签名。
  • :method-combination +:这个关键部分告诉 CLOS 使用求和组合。当 calculate-cost 被调用时,CLOS 会找到所有适用的主方法,调用它们,并将它们的数字结果相加。
  • defmethod:这为特定的参数类型(类)定义了一个具体的实现。请注意,方法定义本身不包含 + 符号;它只返回一个数字。组合逻辑由泛型函数处理。
  • Extensibility:这种方法的强大之处在于,您无需更改原始代码即可为不同类型添加新方法。例如,我们可以通过考虑字符串的长度来定义如何计算涉及字符串的成本。

实际示例:一个多类型成本计算器

Section titled “实际示例:一个多类型成本计算器”

让我们构建一个完整的示例,通过直接相加数字或使用字符串长度来计算“成本”。这演示了 CLOS 如何分派到正确的方法以及 + 组合如何聚合结果。

;; 现代 Lisp 项目通过 ASDF 和 Quicklisp 等系统进行管理。
;; 此代码可以作为 .asd 文件中定义的系统的一部分。
(defpackage #:cost-calculator
(:use #:cl)
(:export #:calculate-cost))
(in-package #:cost-calculator)
;; 1. 使用 '+' 方法组合定义泛型函数。
(defgeneric calculate-cost (x y)
(:documentation "通过汇总专门方法的结果来计算总成本。")
(:method-combination + :most-specific-first))
;; 2. 为不同的类型组合定义方法。
;; 每个方法都是一个“主”方法,并且必须返回一个数字。
(defmethod calculate-cost ((x number) (y number))
"将两个数字相加。"
(+ x y))
(defmethod calculate-cost ((x number) (y string))
"将一个数字与字符串的长度相加。"
(+ x (length y)))
(defmethod calculate-cost ((x string) (y number))
"将字符串的长度与一个数字相加。"
(+ (length x) y))
(defmethod calculate-cost ((x string) (y string))
"将两个字符串的长度相加。"
(+ (length x) (length y)))
;; 3. 演示用法。
(defun run-examples ()
(format t "~&Cost of 10 and 5: ~d" (calculate-cost 10 5))
(format t "~&Cost of 10 and \"test\": ~d" (calculate-cost 10 "test")) ; "test" 的长度为 4
(format t "~&Cost of \"alpha\" and 20: ~d" (calculate-cost "alpha" 20)) ; "alpha" 的长度为 5
(format t "~&Cost of \"hello\" and \"world\": ~d" (calculate-cost "hello" "world")))
;; 要运行此代码:
;; (cost-calculator:run-examples)
Cost of 10 and 5: 15
Cost of 10 and "test": 14
Cost of "alpha" and 20: 25
Cost of "hello" and "world": 10

当调用 (calculate-cost 10 "test") 时,CLOS 会识别出 (number string) 方法是最具体的匹配项。它会调用这个单独的方法。因为只有一个适用方法,+ 组合只是简单地返回结果。当有多个方法(例如来自继承或不互斥的多方法)共同作用时,它的真正威力才显现出来。

  • Type Safety:+ 组合泛型函数的所有主方法必须返回一个数字。返回 nil 或任何其他类型都会在运行时发出 TYPE-ERROR 信号。
  • Readability:当求和结果的行为直观明了时,使用 + 组合。对于更复杂的逻辑,带有 :around 限定符的标准方法可能更易于维护。
  • Zero Contribution:如果一个方法在某些条件下不应对总和做出贡献,它应该明确返回 0。
  • Other Combinations:探索其他内置组合,如 and、or、list、append 和 max,以了解此 CLOS 功能的全部威力。它们遵循相同的原则,但对结果执行不同的操作。