了解 Common Lisp 中的泛型函数?

Vin*_*inn 3 methods function common-lisp clos generic-function

在这个答案中,用户给出了一个非常清晰的示例,说明类和方法如何协同工作。

我将在这里重印该示例:


(defclass human () ())
(defclass dog () ())

(defmethod greet ((thing human))
  (print "Hi human!"))

(defmethod greet ((thing dog))
  (print "Wolf-wolf dog!"))

(defparameter Anna (make-instance 'human))
(defparameter Rex (make-instance 'dog))

(greet Anna) ;; => "Hi human"
(greet Rex)  ;; => "Wolf-wolf dog!"

Run Code Online (Sandbox Code Playgroud)

我的问题是,使用相同的示例:

  1. 创建通用函数会增加什么价值?
  2. 为什么泛型函数有用?它们是否像其他面向对象语言中提供结构的实例一样?

似乎通用函数是在后台隐式创建的(不是 100% 确定)。我注意到,当我使用这个示例时,如果我创建一个具有与该方法的第一个实例不同的参数结构的方法,我会得到一个generic function error.

cor*_*ump 7

创建通用函数会增加什么价值?

我喜欢显式声明泛型函数,因为可以添加与泛型函数相关的文档声明(针对速度/空间/调试进行优化),以及其他细节,例如方法组合(也称为当您有多种适用于给定的调用,这定义了执行哪些以及按什么顺序执行)。例如,我可以定义talk为具有progn方法组合(所有方法都像封装在progn表单中一样执行):

(defgeneric talk (subject)
  (:documentation "Say something to standard output")
  (:method-combination progn))
Run Code Online (Sandbox Code Playgroud)

这是一个有点做作的例子,但我们开始吧:

(defclass human () ())
(defmethod talk progn ((a human))
  (print "hello"))

(defclass wolf () ())
(defmethod talk progn ((a wolf))
  (print "owooooo!"))

(defclass werewolf (human wolf) ())
Run Code Online (Sandbox Code Playgroud)

talk定义从两者继承的类意味着对该类实例的调用可以执行两个方法(按称为方法解析顺序的某种拓扑顺序排序)。因此,通过这种方法组合,所有方法都会被执行:

* (talk (make-instance 'werewolf))
"hello" 
"owooooo!"
Run Code Online (Sandbox Code Playgroud)

但是,我想说,能够记录通用函数本身就是使用defgeneric.

为什么泛型函数有用?

如果您定义talk为通用函数,则允许任何类参与调用的代码talk(例如库),这是一种允许扩展而不必关闭可能值集的方法,这与typecase在函数中使用类似的东西不同,您可以仅列出一组预定义的案例。

例如,print-object在 Lisp 实现中(在检查器、REPL、调试器中)的不同时间调用标准函数,如果您愿意,您可以为自定义类型实现方法,而不必破坏环境的内部结构。

它们是否像其他面向对象语言中提供结构的实例一样?

与其他 OO 语言不同,泛型函数不依赖于单个类或实例。它们可以针对多个参数进行专门化1 ,这意味着它们都不“拥有”通用函数。


1 . 专业定义如下

专门化 vt(泛型函数)来定义泛型函数的方法,或者换句话说,通过为特定的一组类或参数赋予特定含义来细化泛型函数的行为。

这背后的想法是方法可以或多或少具体:一种具有两个参数的方法a,并且b专门针对(a number),并且(b string)比另一种仅专门针对 的方法更具体(b vector),并且方法实际上是从最具体到最不具体的方法排序的(分别从最少到最多)组合它们时。您甚至可以专门化一个函数来(a (eql 10))仅涵盖参数为 10 的特定a情况eql