4 ms·
Isn't it the ambiguity of the Diamond Problem? Suppose B and C are both children of A, and D is a child of both B and C. If B and C both have methods foo(), w
by nvy 2y ago
Isn't it the ambiguity of the Diamond Problem? Suppose B and C are both children of A, and D is a child of both B and C.
If B and C both have methods foo(), which gets called when you do d.foo()?
Seems like a real footgun requiring extra effort to avoid.
- phoe-krk 2y agoIn CL's solution, the order of superclasses matter to avoid ambiguity. If D is defined like (defclass d (b c) ...) then a method specialized on B is called; if like (defclass d (c b) ...) then it's otherwise.
- pfdietz 2y agoAnd sometimes more than one method is invoked, using a sophisticated method combination infrastructure.
- phoe-krk 2y agoRight, I assumed the default method combination, and also the simplest case of it with no around/before/after methods being defined... Golly, CL object system is complicated, now that I look at it from this perspective.
- tmtvl 2y agoIt's simple when you want it, and powerful when you need it.
- jerf 2y agoIn this case the problem becomes that while one can define a 100% consistent, coherent order for the compiler to use, the human's ability to understand what will happen when they call a method of a particular name, and also what that resolution method will do as the code is refactored and changed over time, exceeds anything a human can be reasonably expected to have. Really, all the problems with multiple inheritance are that the humans can't handle the complexity that results. The compilers can be made to do "something" that is arguably sensible.
- Jach 2y agoFortunately in Lisp the compiler is available at runtime! I mean, it's just not that bad. I believe the commercial Lisp IDEs will just show you relevant info much like, say, Java IDEs, but even with a free Lisp you can still ask for it so you don't actually need to wonder what will happen as you're looking at a line. You just ask. The worst part of Lisp vs. C++ on multiple inheritance, I think, where it can be more confusing for Lisp is that Lisp will just overwrite slots (fields) sharing the same name, whereas C++ will shadow them. On the other hand methods aren't owned by individual classes in Lisp, so you get multiple dispatch by default. Lastly the presence of :before / :after / :around methods, combined with multiple dispatch, make it pretty straightforward to achieve behaviors through mixins that require pretty complex contortions otherwise in C++. (Or Java.) The behavior of those "auxiliary methods" is straightforward to reason about. All :before methods run before the most specific primary method, in most-specific-first order, and all :after methods run after the least specific primary method, in least-specific-first order. I'm probably going to convince some people otherwise by giving some more specifics, but as a minor example, consider a silly "game object" style class. I can always ask any class (e.g. an asteroid), hey, what's your class precedence list? (closer-mop:class-precedence-list (find-class 'asteroid)) returns a list of class objects: asteroid, game-object, sprite, add-groups-mixin, cleaned-on-kill-mixin, standard-object, slot-object, and T. From the source code where the defclass is, only game-object is shown. If you look at game-object, only sprite and the two mixins are shown as an example of multiple inheritance. I don't need to call that function to get the info either, it's readily available by calling 'describe on the class. (I think even free editors like Lem or emacs can be configured to automatically show the description of things if you hover over them, I just type ,s in vim.) The description includes the same class precedence list info, tells me the direct superclasses, any subclasses, direct slots (fields directly defined on the class), inherited slots... If I'm wondering what could happen if I call #'kill on an asteroid before I actually call it, I can ask with the built-in 'compute-applicable-methods function or 'closer-mop:compute-applicable-methods-using-classes, and it will show me the applicable methods are firstly the primary defined method, then an :after method due to the mixin. I can also compute the actual effective method that will be called with 'closer-mop:compute-effective-method. For something like #'kill, it shows what happens first is the primary #'kill method, then the :after method. For something like #'draw, let's say I overwrote the base implementation, now it shows there's just one method call, with the potential for the next base class method if the specialized method happens to use 'call-next-method. So in summary, the tools exist in various forms to wrestle the complexity and make it amenable to human understanding. Just like with tools such as cross-referencing, they help understand and create bigger systems, we don't have to limit ourselves to what can easily be done with physical code printouts and hand-made indexes.