4 ms·
Is it really established wisdom that multiple inheritance might be an anti-pattern? Anyone care to elaborate?
by nemoniac 2y ago
Is it really established wisdom that multiple inheritance might be an anti-pattern? Anyone care to elaborate?
- nvy 2y agoIsn'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.
- mark_undoio 2y agoI think the implementation in C++ put it out of fashion, as later languages (e.g. Java) deliberately restricted it to avoid the complexity. The main criticism I saw was the potential for (variants of) the "diamond" where A is subclassed by B and C, then both of those are subclassed by D. Does D get two copies of A's state? It's hard to come up with an intuitive behaviour. More recently, the move seems to be away from class based object orientation (including inheritance) entirely. On the other side of things, I've never heard people talk about Python's multiple inheritance with the same tone used for C++ - but then there are cultural differences in the language communities too.
- fiddlerwoaroof 2y agoSomething I’ve found interesting is that most widely-used class-based inheritance languages eventually added multiple inheritance of implementations back in: PHP added traits that can contain method implementations; Java added default implementations on interfaces; etc.
- lmm 2y agoThe famous "super considered harmful" post ( https://fuhm.net/super-harmful/ https://fuhm.net/super-harmful/ ) pointed out the key problem with diamonds, and it's mainly a problem with constructors. Allowing mixins that can have method implementations but only allowing one class parent with a constructor is a pretty good spot in the design space, and is what a lot of languages have converged on.
- fiddlerwoaroof 2y agoI like CL’s solution to constructors which is basically “specialize this generic function (SHARED-INITIALIZE or INITIALIZE-INSTANCE) with an :AFTER method”. You reliably run all the initialization code for each class involved and you don’t have to remember to call CALL-NEXT-METHOD (CL’s spelling of super) Edit: I see that post refers to Dylan, which is more like CL than python in the important ways. IMO, sleeping on CL’s object system CLOS was a huge mistake of the “Java/C++ era” of our industry.
- deleted 2y ago[deleted]
- pfdietz 2y agoA nice pattern from Common Lisp is to inherit the parts of an object from different superclasses. Method combination means one can write methods for those superclasses and then have them automatically combined in a subclass. Example: if one has tree nodes with various slots that represent children and you want to write a tree traversal function, you put each slot in a superclass, inherit from those superclasses in the correct order, and then write a method for each superclass that calls the child at that slot. The methods are combined in the right order automatically in a PROGN method combination.
- jolt42 2y agoMeh. Probably a reaction to getting "burned" by it. But show me something you can't get burned by.
- copx 2y ago90s-style Java OOP showed everyone that heavy use of multiple inheritance is the worst thing since 80s-style BASIC where ever third line was a GOTO. Imagine one class inheriting from 50 other classes through multiple inheritance.. People really used to construct classes like: "Iron Sword inherits from Iron which inherits from Metal which inherits from Meltable (which inherits from Temperature) and Material. But of course it also inherits from Sword which inherits from Weapon and Edged. Meanwhile Weapon inherits from Equipment which inherits from Ownable and Item which.." and so on. Basically you make every aspect and attribute of an entity a class and then create your entity's class by mushing together all those classes through multiple inheritance. The results are..not pretty. Such code quickly becomes very hard to comprehend and maintain.
- mikepurvis 2y agoYup. No amount of generated documentation or static analysis can make up for the cognitive load required to reason about where a particular method is actually being dispatched to under those conditions.
- deleted 2y ago[deleted]
- Jtsummers 2y ago90s Java did not have multiple inheritance (nor does today's Java). It did have multiple interfaces, but they only carried a spec of the interface and no implementation details beyond that. C++ was the one with multiple inheritance, if you are trying to reference a popular 90s OO language.
- anthk 2y agoOOP would work fine for a text adventure, such as Inform6 against the Z-Machine, which pretty much the gameplay rooms->objects it's perfect for this. For everything else... well... maybe just CLOS it's usable enough.
- cess11 2y agoThe MUD-family of games are usually built in a C-like OOP-language, LPC. I think it's rather nice.
- dangmumwhore 2y ago[flagged]