Multiple Inheritance Part 1: Problems with the existing design

I’m back from my vacation, and it’s time to get to one one the biggest issues pointed out in the feedback we received during conference presentations and in the comments to the docs. I’m talking about inheritance.

I plan to write a series of posts on this topic. These posts are intended to provoke a discussion, so that we can benefit from your feedback and come up with a better design.

This is the first post in the series, and I discuss the design we presented in July 2011. It features the following approach to inheritance:

  • there were no interfaces, only classes;
  • each class could have multiple superclasses;
  • if some non-abstract member (property or method) was inherited from two of the supertypes, the compiler required the user to override it and specify manually what code to run.

(For more details, see our wiki as of July 20th 2011.)

This is, basically, the infamous multiple inheritance story, and we remember from the C++ times that it is sort of bad. Let’s look closer.

It’s all about initialization

Let’s a look at the following example:

So, we have a diamond: Base at the top, Left and Right on the sides, and Child at the bottom. One thing looks suspicious here: Child initializes its superclasses passing different numbers two them: 3 to Left and 4 to right. Now, they, in turn, initialize Base with those numbers… What is Base initialized with?

Actually, there are two “instances” of Base created: one, initialized with 3, is hidden inside Left(3), and another, initialized with 4 — inside Right(4). I.e. it works like non-virtual inheritance in C++. (On the Java platform, we implemented it by delegation, which is invisible for the user.)

Now, what happens when you call a function that is defined in Base? For example, let’s say that Base defines two abstract functions:

Now, let Left override foo() and Right override Bar:

In this case Child inherits two declarations of foo() and two declarations bar(), but at the same time it inherits only one implementation for each of these functions, so it’s OK, the behavior is determined. So, when we say

The output is

Because foo() was called for Left, and bar() was called for Right.

If Child inherited more than one implementation of, say, foo(), the compiler would have complained until we override foo() in Child and specify the behavior explicitly. So, we are guaranteed to have no ambiguity when calling functions of Child.

So far, so good, but there still is something wrong with this approach…

Problem 1: the constructor for Base is called twice whenever we create an instance of Child. It’s bad because if it has side-effects, they are duplicated, and the author of the Child class may not know about it, because someone change the inheritance graph turning it into a diamond that was not there before.


Problem 2: the implementation of Left assumes it’s initialized with 3, but it may call bar() that is implemented in Right and assumes everything is initialized with 4. This may cause some inconsistent behavior.

Problem 3: being implemented by delegation, deep hierarchies will degrade performance by having long delegation chains.

(Im)Possible ways of fixing it

Now, how can we fix our design? C++ copes with Problems 1 and 3 by having virtual inheritance. On the Java platform and with separate compilation in mind, I do not think we can get rid of delegation when a class inherits state from two sources, so the Problem 3 stands for us anyway. And having two flavors of inheritance is no good, as we learned from C++…

Virtual inheritance does not fix Problem 2: being initialized differently, parts of the inherited implementation may make inconsistent assumptions about the overall state of the object. This problem seems intractable in the general case, but let’s be accurate and make sure it really is.

We could try to guarantee that everything is initialized consistently. In the general case, when we pass arbitrary expressions to Left and Right, there’s no way to be sure they yield same results, even if they are textually the same. Then, we could impose some constraints here. For example: only allow to pass compile-time constants or immutable variables to superclass constructors. In this case the compiler could examine the whole class hierarchy and make sure every base class is initialized consistently. There is a problem, though: if one of the superclasses change its initialization logic even slightly, subclasses may become inconsistent, so this will be a big evolution problem, for example, for libraries.

And, of course, it would be too restrictive to impose those constraints on all classes. So we end up with two flavors of classes…

Well, it seems that “there are only classes (i.e. no interfaces or alike)” approach did not work out. Now, it’s time to consider other approaches.

What’s out there

Different languages manage multiple inheritance differently, and I summarize some of the approaches here.

  • Java and C# have classes and interfaces, i.e. multiple interface inheritance and single implementation inheritance;
  • Scala has classes and traits that may implement methods and even have state, but their constructors can not have parameters;
  • Some other languages, like Fortress, do not allow state in traits;
  • <Your favorite language here>

In the next post of this series we will discuss the options in detail.

And now it’s time for your comments. They are very welcome.

About Andrey Breslav

Andrey is the lead language designer working on Project Kotlin at JetBrains. He also works on making the Java language better, serving as a Java Community Process expert in a group for JSR-335 ("Project Lambda"), and occasionally speak at software conferences such as Devoxx or JavaOne.
This entry was posted in General, Language design and tagged . Bookmark the permalink.

17 Responses to Multiple Inheritance Part 1: Problems with the existing design

  1. Graham Nash says:

    I would like to venture another, perhaps more controversial option. Drop support for class implementation inheritance entirely in favor of interfaces and delegation. Implementation inheritance is a feature that is more abused than used correctly and with such good support for delegation in Kotlin, the need for implementation inheritance is largely removed.

    • As I mentioned in the post, it implies a huge performance penalty of long delegation chains.

      Another thing is that with delegation you easily run into the following situation:
      let A delegate to B and override method foo() of B,
      now, let someone call a method bar() that’s defined in B and calls foo(),
      you can see the problem: the overridden implementation does not get called, and this makes behavior inconsistent, i.e. introduces a bug that is hard to track down.

      • Aivar Annamaa says:

        This problem (with delegation not being “complete”) can also occur with inheritance:

        let A be subclass of B and override method foo() of B.
        Let’s imagine that foo() is implemented in B as a plain wrapper over a private method _foo() that all
        other B’s methods use.
        now, let someone call a method bar() that’s defined in B and calls _foo(),
        again the overridden implementation does not get called.

        Seems that, be it delegation or inheritance, you can only change certain view to the object. You can’t be sure what will be the exact effect of the override (if you don’t control the base class/object).

        • Aivar Annamaa says:

          Oops, at first line I meant: with *override* not being “complete”.

        • The point is that with delegation there’s NO WAY of creating a foo() that’s overridden properly, and with inheritance there IS a way. That’s why we are not abandoning one in favor of the other.

          • Aivar Annamaa says:

            Overriding could be also replaced with parameterization — with constructor taking functions as parameters. This would make the possible extension points explicit. (And there could be even default values for those parameters.)

            BTW, I’m just tossing thoughts around here. I don’t think I’d like to actually give up inheritance. But seems that the inheritance is the most hackish part of traditional OOP.
            (Then again, aspects are even bigger hack, but they are still useful …)

          • Yes, sure we can replace overriding by explicitly exposing extension points as constructor parameters. There would be a few problems, though:
            * How do you manage visibility: a member function sees the parent class’ protected members, but a function literal passed from the outside does not
            * How do you call a super method from an overriding function?

            I agree that ad-hoc polymorphism is hackish, but it sure works pretty well so far.

          • Aivar Annamaa says:

            I think that, in case of those parameter-methods, the inability to call “super” object (or client in this case) can be the reason to prefer such approach — code will be more composable.

            As I’ve understood, it’s the (possibly) bidirectional dependence between methods defined in superclass and methods defined in subclass, which can cause those maintenance problems (eg. “fragile base class”). With parameter-methods and delegation (eg. decoration), the dependence is unidirectional.

            Yep, inheritance works well – it’s powerful and is also intuitively quite easy to understand. I’m just wondering whether it pays off to use a less flexible and less powerful but more predictable/stable approach. It’s somewhat similar to functional vs. imperative dilemma.

            Some of my students in introductory programming course currently have “global variables” vs. function parameters dilemma. I guess this pushed me to question some easy to use and powerful things :)

          • I’d say it’s an appealing idea to have things cleaner, and I hope it gets through some day. Currently the problem is that those people, who struggle with new concepts, actually make decisions that influence adoption of a language/paradigm :)

  2. This post raises many questions worth answering before committing to a MI implementation, and references a wide range of implementations and trade offs:

    Setting Multiple Inheritance Straight
    by Michele Simionato
    January 10, 2009
    http://www.artima.com/weblogs/viewpost.jsp?thread=246488

    All considered, Traits may be the simplest thing that could possibly work if the least magical. Simionato does argue well, though, that you can get by without them altogether:

    Generic functions vs mixins: a case study
    by Michele Simionato
    September 3, 2008

    And I believe Kotlin may the machinery to support this with extension functions using extension properties rather than field access.

  3. Jan Kotek says:

    Hi,

    I would throw an compilation exception if Left and Right would have the same superclass. Just refuse to compile diamonds.

  4. Pingback: Multiple Inheritance Part 2: Possible directions | Project Kotlin

  5. FIEK says:

    Can you anybody tell me when is difference for the code with multiple inheritance and without multiple inheritance??? :S

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">