There are others that are also _very_ interesting (but also pre 90s). SELF fe, was object oriented, but did not have inheritance. It used prototypes. In essence, you would create something of the same "type" by cloning the prototype.
Anyway,
subtyping and inheritance are similar, but not identical, concepts. Also, some languages provide traits iso protocols (and maybe even both ?). Some languages provide functors. The goal is always the same: abstract commonalities. Let's just keep it at: "it's complicated"
;)
I think it's better to study the design mistakes of programming languages in a historic context.
For example: C++ offers multiple inheritance. This caused the diamond problem. Java tried to fix this via interfaces. This fixed the problem, but was also a mistake as interfaces cannot provide behaviours. So Multiple inheritance is not an issue if only 1 of the parties provides state; all others can provide signatures but also behaviours.
> So Multiple inheritance is not an issue if only 1 of the parties provides state; all others can provide signatures but also behaviours.
IIRC that's what Bertrand Meyer advised in OOSC (I think he called it "marriage of convenience" between abstract and concrete superclasses). But he also claimed the diamond problem is overstated, and (paraphrasing) it's not a deep semantic issue, but a trivial syntactic one, "simply" solved with rename-on-inherit :)
It's not easily solved with a rename on inherit, because the resulting object has to be substitutable in all the places where any one of the multiple base classes are expected. Those places expect the names to be what they are, they don't know about renamed names.
The diamond problem is actually about the situation when through at least two levels of inheritance, a class ends up inheriting the same base two or more times.
C++ has two choices for the diamond problem: virtual base inheritance results in one copy. Regular inheritance in multiple copies.
The clash problem is separate from the diamond problem. A clash occurs when you inherit from two bases that use the same names, but are usually separate bases. For instance a lottery game class inherits graphics and lottery; the former provides graphics::draw and the latter lottery::draw.
In C++ that is dealt with by leaving the name lookup be ambiguous. When the derived game object is used like this: game.draw(...), the name lookup is ambiguous. The program has to specify game.lottery::draw(...) or game.graphics::draw().
Places in the program that use the object through references to one of the bases do not face the ambiguity. Given a graphics &gobj, gobj.draw() is unambiguous, even if that object is really a game class instance that also has lottery::draw in it.
Scope resolution operator can resolve the ambiguity under the diamond problem, when inheritance is plain (not virtual). Say A inherits B and C. Both B and C inherit D. So now A has two D's. Say D has a member m. Given an A object aobj,I think we can separately reference aobj.B::D::m to get to the D::m that was inherited via B, and aobj.C::D::m to get to the copy inherited via C.
I haven't really seen the diamond problem referenced lately as an objection to inheritance, but I honestly never understood what the big deal was. In the event of a collision, the compiler can ask for further disambiguation, which does in fact boil down to an issue of syntax, not one of a soundness or correctness violation.
Python is probably the most CLOS-like language in common use tho, isn't it? Multiple inheritance, metaclasses, the ability to decorate functions.
No built-in multiple dispatch but I'm pretty sure I saw some implementations of that too over the years :)
Someone doesn't know Common Lisp.