钻石继承和通用Lisp对象系统

5

我正在尝试解决Common Lisp CLOS中典型的钻石继承问题。以下是代码:

(defclass C1.0 () ... )
(defclass C2.1 (C1.0) ...)
(defclass C2.2 (C1.0) ...)
(defclass C3.0 (C2.1 C2.2) ...)

(defmethod m1 ((obj C1.0)) ...)
(defmethod m1 ((obj C2.1)) ...)
(defmethod m1 ((obj C2.2)) ...)
(defmethod m1 ((obj C3.0))
  ; Here I want to call the C2.2 version of
  ; m1
  ...)

还假设C1.0、C2.1和C2.2的代码在一个我无法访问的库中,因此我无法对其中任何内容进行修改。进一步地,还要假设其他一些类也将从C2.2派生,而且可能不想调用C2.2版本的m1,因此我不能使用:before向C2.2添加任何内容。使用call-next-method将调用C2.1版本。
仅供澄清,以下是如何在Python中解决该问题的方法:
class C1_0 :
  def m1 (self) : ...

class C2_1 (C1_0) :
  def m1 (self) : ...

class C2_2 (C1_0) :
  def m1 (self) : ...

class C3_0 (C2_1, C2_2) :
  def m1 (self) :
    C2_2.m1 (self)     # <-- This is the solution
    ...
3个回答

8
如果你想调用特定类的方法,那么你就打破了CLOS(公共Lisp面向对象系统)的初衷。此外,CLOS比此更加通用,因为它不仅支持多重继承,还支持多重分派。分派可以作用于多个参数。因此,方法不属于类,而方法的继承不是基于类继承,而是基于方法组合(通常使用类继承以某种方式排序方法)。
让我们看一下两个参数分派的方法:
(defmethod m1 ((obj1 C1.0) (obj2 C1.0)) ...)
(defmethod m1 ((obj1 C2.1) (obj2 C1.0)) ...)
(defmethod m1 ((obj1 C2.2) (obj2 C3.0)) ...)
(defmethod m1 ((obj1 C3.0) (obj2 C3.0)) ...)

请注意,我们谈论的是“标准方法组合”,当您调用通用函数时,通常会调用最具体的主要方法。在CLOS中,“标准方法组合”中的可适用方法可以通过“call-next-method”调用“next”方法。这是直接调用继承功能的常规机制(还有“:before”,“:after”和“:around”方法可能提供继承功能)。
但是另一种(简单的)方法组合,例如“+”,“progn”或“and”呢?在“+”的情况下,所有适用的方法都将被调用,然后将结果相加。
然后我们有像这样的方法:
(defmethod m1 + ((obj1 C1.0) (obj2 C1.0))  1)
(defmethod m1 + ((obj1 C2.1) (obj2 C1.0))  2)
(defmethod m1 + ((obj1 C2.2) (obj2 C3.0)) 10)
(defmethod m1 + ((obj1 C3.0) (obj2 C3.0)) 20)

像这样的简单方法组合在应用程序或库中并不经常使用。CLOS 还提供了一种用户指定的复杂方法组合方式(例如:用户可以在 CLOS 中实现“按合同设计”功能)。

在 CLOS 中,您可以解决它:您可以访问通用函数的特定方法并调用其方法函数,但这很少使用且已经是元级功能。您还可以编写自己的方法组合,在其中提供更复杂的调用方法。但那也相当困难。

因此,只有当您以类为导向的对象系统思考并尝试将 CLOS 用作这样的系统时,才有必要考虑 CLOS 中的“菱形问题” - 这在简单情况下有点可能通过将 CLOS 用法限制在简单情况下来实现。但是然后 CLOS 没有一个“解决方案”来解决“菱形问题”。对于插槽,CLOS 通过将相同插槽合并为一个来避免它。对于方法,CLOS 提供了一种不同的方法将方法组织成通用类并调用它们,首先通过“方法组合”将它们组装起来。


非常感谢您的澄清。我仍在学习LISP,而且来自像Python和C++这样的语言背景,有时候我会忘记CLOS与“通常”的面向对象系统的区别,尽管我正在努力减少这种情况,但效果还不够好 :) - RKS

0

我在Clozure CL中找到了一种方法,但不确定它是否在主要的Common Lisp实现中是可移植的(或半可移植的)。

以下代码将调用m1的C2.2版本:

(funcall
  (method-function (find-method #'m1 '() `(,(find-class 'C2.2))))
  obj)

0

通过改变超类的顺序,可以改变调度的顺序:

(defclass c3.2 (c2.2 c2.1)
  ...)

(defclass c3.1 (c2.1 c2.2)
  ...)

(m1 (make-instance 'c3.2)) ; calls the method specialized to c2.2

(m1 (make-instance 'c3.1)) ; calls the method specialized to c2.1

如果我想在其他函数中保留原始顺序呢? - RKS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接