Diamond inheritance and the Common Lisp Object System
Asked Answered
B

3

5

I am trying to find a solution to typical diamond inheritance problem in Common Lisp CLOS. The code :

(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
  ...)

Also assume that the code of C1.0, C2.1 and C2.2 is in a library which I dont have access to, so I cannot modify anything there. Furthur, assume that some other classes will also derive from C2.2 and MAY NOT want to call C2.2 version of m1, so I cannot really add anything to C2.2 using :before. Using call-next-method will call C2.1 version.

Just for clarification, this is how to solve it in 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
    ...
Bennink answered 22/11, 2014 at 4:46 Comment(0)
M
8

If you want to call specific methods of classes you are defeating the purpose of CLOS. Note also that CLOS is more general than this, since it does not only support multiple inheritance, but also multiple dispatch. Dispatch can work on more than one argument. Thus methods don't belong to classes and inheritance of methods is not based on class inheritance, but on method combinations (which usually uses class inheritance to order methods in some way).

Let's see methods which dispatch on two arguments:

(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)) ...)

Note that we talk about standard method combination, where typically the most specific primary method gets called, when you call the generic function. In CLOS the applicable methods in standard method combination can call the next method via call-next-method. That's the usual mechanism to directly invoke inherited functionality (there are also :before, :after and :around methods which may provide inherited functionality).

But what about another (simple) method combination, like +, progn or and? In the case of + all applicable methods get called and the results are added.

Then we have methods like:

(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)

Simple method combinations like that are not often used in applications or libraries. CLOS also provides a way for user-specified complex method combinations (example: design by contract functionality can be implemented by the user in CLOS).

You can work around it in CLOS: You can get access to specific methods of a generic function and call its method function, but this is rarely used and already a meta-level functionality. You can also write your own method combination, where you can provide a more complex calling method. But that is also quite difficult.

Thus it may make sense to think of a 'diamond problem' in CLOS only if you think in terms of class-oriented object systems and try to use CLOS as such - which is kind of possible by limiting CLOS usage to simple cases. But then CLOS does not have a solution to the diamond problem. CLOS avoids it for slots by merging same slots into one. CLOS avoids it for methods by providing a different way to organize methods into generic classes and calling them, by first assembling them via method combination.

Milner answered 22/11, 2014 at 9:45 Comment(1)
Thanks a lot for clarifying. I am still learning LISP, and coming from the background of languages like Python and C++, sometimes I tend to forget the difference of CLOS from "usual" object oriented system, something I am trying to minimize, not successful enough though :)Bennink
B
0

I have found a way in Clozure CL, but not sure its portable (or semi-portable) across major Common Lisp implementations.

Following code will call the C2.2 version of m1 :

(funcall
  (method-function (find-method #'m1 '() `(,(find-class 'C2.2))))
  obj)
Bennink answered 22/11, 2014 at 5:23 Comment(0)
S
0

The order of dispatch can be changed by changing the order of the superclasses:

(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
Sponsor answered 22/11, 2014 at 6:47 Comment(1)
What if I want the original order in other functions ?Bennink

© 2022 - 2024 — McMap. All rights reserved.