【问题标题】:Diamond inheritance and the Common Lisp Object SystemDiamond 继承和 Common Lisp 对象系统
【发布时间】:2015-01-20 08:29:38
【问题描述】:

我正在尝试在 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 并且 可能不会 想要调用 m1 的 C2.2 版本,所以我不能使用 :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
    ...

【问题讨论】:

    标签: inheritance lisp common-lisp diamond-problem clos


    【解决方案1】:

    如果你想调用类的特定方法,你就违背了 CLOS 的目的。另请注意,CLOS 比这更通用,因为它不仅支持多重继承,还支持多重分派。 Dispatch 可以处理多个参数。因此方法不属于类,方法的继承不是基于类继承,而是基于方法组合(通常使用类继承以某种方式对方法进行排序)。

    让我们看看在两个参数上分派的方法:

    (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 方法可以提供继承功能)。

    但是另一个(简单的)方法组合呢,比如+prognand?在+ 的情况下,调用所有适用的方法并添加结果。

    然后我们有这样的方法:

    (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 与“通常”的面向对象系统的区别,我试图将其最小化,但还不够成功 :)跨度>
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

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

      (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
      

      【讨论】:

      • 如果我想要其他功能中的原始顺序怎么办?
      猜你喜欢
      • 1970-01-01
      • 2011-06-03
      • 1970-01-01
      • 2021-12-18
      • 1970-01-01
      • 1970-01-01
      • 2019-01-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多