Jump to page: 1 2
Thread overview
Suggestion: Walter Bright, why not to implement multiple inheritance in D?
Dec 01, 2006
Justin C Calvarese
Dec 01, 2006
Gregor Richards
Dec 01, 2006
Gregor Richards
Dec 02, 2006
Kyle Furlong
Dec 02, 2006
Burton Radons
Dec 02, 2006
Larry Evans
Dec 03, 2006
Kyle Furlong
Dec 03, 2006
Walter Bright
Dec 10, 2006
Larry Evans
Dec 13, 2006
Walter Bright
Dec 21, 2006
Larry Evans
Dec 03, 2006
Burton Radons
Dec 04, 2006
Larry Evans
Dec 02, 2006
Burton Radons
Dec 02, 2006
Walter Bright
Dec 02, 2006
BCS
December 01, 2006
Recently, I've started the subject about multiple inheritance in D. I've got the answers. But those solutions are not enough suitable for my purposes. Then I thought, why not to implement the feature which is on demand by programmers in D? Multiple inheritance is needed indeed just only by seeing the attempts of programmers to emulate it in D. It means they need it. Multiple inheritance is really not that bad thing which is supposed to be got rid of. Just a linear multiple inheritance with possibility to get to the every level of its hierarchy is enough, I think. Moreover, I'll help to port many useful C++ code into D much easier than now.

I know two approaches by now, but both are restrictive to feel the freedom of multiple inheritance.

First approach is excellent, but does not allow you to get as deep as you want to the class hierarchy (only super's virtual methods are accessible explicitly):

//////////////////////////////////
interface I
{
}

class A(T) : T, I
{
}

class B(T) : T, I
{
}

class C(T) : T, I
{
}

alias C!(B!(A(Object))) D;
////////////////////////////////

Second approach is excellent too, but lacks template merging possibilities and pointing explicitly which interface function to implement (on name collisions):

//////////////////////////////////////
interface A
{
}

interface B
{
}

interface C
{
}

template A_()
{
}

template B_()
{
}

template C_()
{
}

class D : A, B, C
{
   mixin A_;
   mixin B_;
   mixin C_;
}
////////////////////////////////

Walter, is it a matter of principle not to implement multiple inheritance in
D? If D is the language of practical usage then multiple inheritance must be
considered to implement.
What do you think?
December 01, 2006
Arlen Albert Keshabyan wrote:
> Recently, I've started the subject about multiple inheritance in D. I've got
> the answers. But those solutions are not enough suitable for my purposes. Then
> I thought, why not to implement the feature which is on demand by programmers
> in D? Multiple inheritance is needed indeed just only by seeing the attempts
> of programmers to emulate it in D. It means they need it. Multiple inheritance
> is really not that bad thing which is supposed to be got rid of. Just a linear
> multiple inheritance with possibility to get to the every level of its
> hierarchy is enough, I think. Moreover, I'll help to port many useful C++ code
> into D much easier than now.

This has been discussed to death in the past.

Feel free to read up on the past arguments. Here's a couple threads to get you started (I'm sure more threads are out there)...

16 Aug 2001: http://www.digitalmars.com/d/archives/43.html
18 Mar 2004: http://www.digitalmars.com/d/archives/25807.html


I think the bottom line is the multiple inheritance would added too much complexity for too little benefit. If the program has been designed in C++, it might need some re-design to bring it into D.

-- 
jcc7
December 01, 2006
Arlen Albert Keshabyan wrote:
> Recently, I've started the subject about multiple inheritance in D. I've got
> the answers. But those solutions are not enough suitable for my purposes. Then
> I thought, why not to implement the feature which is on demand by programmers
> in D? Multiple inheritance is needed indeed just only by seeing the attempts
> of programmers to emulate it in D. It means they need it. Multiple inheritance
> is really not that bad thing which is supposed to be got rid of. Just a linear
> multiple inheritance with possibility to get to the every level of its
> hierarchy is enough, I think. Moreover, I'll help to port many useful C++ code
> into D much easier than now.
> 
> I know two approaches by now, but both are restrictive to feel the freedom of
> multiple inheritance.
> 
> First approach is excellent, but does not allow you to get as deep as you want
> to the class hierarchy (only super's virtual methods are accessible explicitly):
> 
> //////////////////////////////////
> interface I
> {
> }
> 
> class A(T) : T, I
> {
> }
> 
> class B(T) : T, I
> {
> }
> 
> class C(T) : T, I
> {
> }
> 
> alias C!(B!(A(Object))) D;
> ////////////////////////////////
> 
> Second approach is excellent too, but lacks template merging possibilities and
> pointing explicitly which interface function to implement (on name collisions):
> 
> //////////////////////////////////////
> interface A
> {
> }
> 
> interface B
> {
> }
> 
> interface C
> {
> }
> 
> template A_()
> {
> }
> 
> template B_()
> {
> }
> 
> template C_()
> {
> }
> 
> class D : A, B, C
> {
>    mixin A_;
>    mixin B_;
>    mixin C_;
> }
> ////////////////////////////////
> 
> Walter, is it a matter of principle not to implement multiple inheritance in
> D? If D is the language of practical usage then multiple inheritance must be
> considered to implement.
> What do you think?

I'm not Walter, and I'm no expert in the implementation of languages, and I am in fact guilty of using multiple inheritance often enough...  But I will say this much.  I've been working on implementing my own BovisMOO language for a project, and within Bovis I've allowed for multiple inheritance, despite its predecessor (LambdaMOO) being strictly single inheritance.  Which caused a very simple lookup which looked roughly (pseudo-code) like this:

#  while (obj && !(pname in obj.fieldDefinitions)) { obj = obj.parent; }
#  if (obj is null) {
#    // no such property error, throw an exception
#  }
#  // do work with the property

To now look like this:

#  if (!(pname in obj.fieldDefinitions)) {
#    foreach (o; &obj.traverseAncestryBreadthFirstEnsuredOnce) {
#      if (pname in o.fieldDefinitions) {
#        obj = o;
#        break;
#      }
#      if (o is Database.instance.rootObject) {
#        // no such property error, throw an exception
#      }
#    }
#  }
#  // so do work with the property

All off the top of my head, but that's more-or-less how it goes.  Yes, it does work, but it sure became a lot uglier and a lot less efficient... and this is just for a weak-typed little scripting language with no concepts of classes or interfaces!  I can only imagine what horrors it might lead to in a full systems programming language with rich OOP features.  I'd say the main reason it isn't there, is to prevent Walter from having migraines.  And I don't consider that a completely unmerited excuse.  :)

-- Chris Nicholson-Sauls
December 01, 2006
Arlen Albert Keshabyan wrote:
> Recently, I've started the subject about multiple inheritance in D. I've got
> the answers. But those solutions are not enough suitable for my purposes. Then
> I thought, why not to implement the feature which is on demand by programmers
> in D? Multiple inheritance is needed indeed just only by seeing the attempts
> of programmers to emulate it in D. It means they need it. Multiple inheritance
> is really not that bad thing which is supposed to be got rid of. Just a linear
> multiple inheritance with possibility to get to the every level of its
> hierarchy is enough, I think. Moreover, I'll help to port many useful C++ code
> into D much easier than now.
> 
> I know two approaches by now, but both are restrictive to feel the freedom of
> multiple inheritance.
> 
> First approach is excellent, but does not allow you to get as deep as you want
> to the class hierarchy (only super's virtual methods are accessible explicitly):
> 
> Walter, is it a matter of principle not to implement multiple inheritance in
> D?

No.

> If D is the language of practical usage then multiple inheritance must be
> considered to implement.

Yeah, obviously. I mean, who the hell uses Java or C#?

> What do you think?

Multiple inheritance is a terrible idea on at least two levels:

1) In class trees with functions or (worse yet) variables with the same name at different levels, it's confusing to the programmer to figure out what name maps to what.

2) The way OO is implemented in 99% of compiled programming languages, multiple inheritance is impossible. Let me explain:

// this is the structure representing an object
struct Object {
    struct ClassInfo *classInfo;
    struct RandomGarbage whateverTheLanguageNeeds;
    // variables go here
}

// this is the structure representing a class
struct ClassInfo {
    struct ClassInfo *parent;
    // <many other things>
    void **vtable;
}


vtable is an array of function pointers, like so for example
class A.vtable =
0 -> foo()
1 -> foo(int)

now imagine we have a class derived from the one described above. To make it usable as the base class, it has a vtable like this
class A(B).vtable =
0 -> foo()
1 -> foo(int)
2 -> bar()
3 -> bar(float)

Hey presto! If you pass in an object with this vtable, it will work for both, because calling the function foo just involves calling vtable[0]!


Now, imagine the multiple-inheritance scenario. We have these two classes:
class A.vtable =
0 -> foo()
1 -> foo(int)

class B.vtable =
0 -> baf()
1 -> baf(Object)

Now we'd like to make a class C derived from both A and B:
class C(A, B).vtable =
0 -> ???
1 -> ???
2 -> (C's own functions)

Now can you see the problem? We can't simply dereference vtable[0] or vtable[1] to get the proper function, because there are two different classes, each with their own vtable[0] and vtable[1].

There are solutions to this (obviously, since C++ works), but they're usually arcane and always inefficient.

 - Gregor Richards
December 01, 2006
Addendum:
Incidentally, it's usually considered bad form to use multiple inheritance in C++.

 - Gregor Richards
December 02, 2006
Gregor Richards wrote:
> Arlen Albert Keshabyan wrote:
>> Recently, I've started the subject about multiple inheritance in D. I've got
>> the answers. But those solutions are not enough suitable for my purposes. Then
>> I thought, why not to implement the feature which is on demand by programmers
>> in D? Multiple inheritance is needed indeed just only by seeing the attempts
>> of programmers to emulate it in D. It means they need it. Multiple inheritance
>> is really not that bad thing which is supposed to be got rid of. Just a linear
>> multiple inheritance with possibility to get to the every level of its
>> hierarchy is enough, I think. Moreover, I'll help to port many useful C++ code
>> into D much easier than now.
>>
>> I know two approaches by now, but both are restrictive to feel the freedom of
>> multiple inheritance.
>>
>> First approach is excellent, but does not allow you to get as deep as you want
>> to the class hierarchy (only super's virtual methods are accessible explicitly):
>>
>> Walter, is it a matter of principle not to implement multiple inheritance in
>> D?
> 
> No.
> 
>> If D is the language of practical usage then multiple inheritance must be
>> considered to implement.
> 
> Yeah, obviously. I mean, who the hell uses Java or C#?
> 
>> What do you think?
> 
> Multiple inheritance is a terrible idea on at least two levels:
> 
> 1) In class trees with functions or (worse yet) variables with the same name at different levels, it's confusing to the programmer to figure out what name maps to what.
> 
> 2) The way OO is implemented in 99% of compiled programming languages, multiple inheritance is impossible. Let me explain:
> 
> // this is the structure representing an object
> struct Object {
>     struct ClassInfo *classInfo;
>     struct RandomGarbage whateverTheLanguageNeeds;
>     // variables go here
> }
> 
> // this is the structure representing a class
> struct ClassInfo {
>     struct ClassInfo *parent;
>     // <many other things>
>     void **vtable;
> }
> 
> 
> vtable is an array of function pointers, like so for example
> class A.vtable =
> 0 -> foo()
> 1 -> foo(int)
> 
> now imagine we have a class derived from the one described above. To make it usable as the base class, it has a vtable like this
> class A(B).vtable =
> 0 -> foo()
> 1 -> foo(int)
> 2 -> bar()
> 3 -> bar(float)
> 
> Hey presto! If you pass in an object with this vtable, it will work for both, because calling the function foo just involves calling vtable[0]!
> 
> 
> Now, imagine the multiple-inheritance scenario. We have these two classes:
> class A.vtable =
> 0 -> foo()
> 1 -> foo(int)
> 
> class B.vtable =
> 0 -> baf()
> 1 -> baf(Object)
> 
> Now we'd like to make a class C derived from both A and B:
> class C(A, B).vtable =
> 0 -> ???
> 1 -> ???
> 2 -> (C's own functions)
> 
> Now can you see the problem? We can't simply dereference vtable[0] or vtable[1] to get the proper function, because there are two different classes, each with their own vtable[0] and vtable[1].
> 
> There are solutions to this (obviously, since C++ works), but they're usually arcane and always inefficient.
> 
>  - Gregor Richards

The Gregor (tm) strikes again!
December 02, 2006
As an aside to Walter, I stumbled on a solution to the multiple vtable problem a couple years ago. Instead of putting the vtable in the object, you put it in the reference, like with a delegate. Then if an object is posing as a base type, you just select an appropriate vtable and offset the pointer. That way you don't need to worry about trying to fit vtables into base types, although you still need to put offsets into the vtable for mutual base types in the (A; B : A; C : A; D : B, C) case for field pointers. Didn't say it was a panacea.

It's no good for D, though. Too bad because (even without MI) it would easily allow other functionality. There's no reason then to keep value and reference types separate, for example, and a mostly-equivalent vtable can be selected based upon contextual needs. I had an example for why you'd want to do that around but I can't remember it now.

Ah, here's one: say you take a reference of a middle value in an array. If someone tries to delete this reference, you can either delete the whole array (bad), sigsegv (bad), ignore it because it's not a pointer to the beginning of the block (bad because if it were a reference to the first array element it would still be nonsensical to delete it but it would work), ignore it because it's a reference to an array element (better, and possible with the scheme here), or remove it from the array (which is probably best but depends upon storing the length with the array data, which might be more correct overall but has too many bad points to counter its few good ones. But if arrays WERE implemented like that, this scheme would allow that to happen efficiently).

It'd remove the need for boxing, too, since you could just convert a value into a reference, cast it to void&, then try to cast it back into a value type.

It's a little faster on dispatch, of course; one less pointer to follow. Likely a little slower when all's considered.

Oh well, stuff for my language, if I ever get around to that.
December 02, 2006
Gregor Richards wrote:
> Now we'd like to make a class C derived from both A and B:
> class C(A, B).vtable =
> 0 -> ???
> 1 -> ???
> 2 -> (C's own functions)
> 
> Now can you see the problem? We can't simply dereference vtable[0] or vtable[1] to get the proper function, because there are two different classes, each with their own vtable[0] and vtable[1].
> 
> There are solutions to this (obviously, since C++ works), but they're usually arcane and always inefficient.

The solutions to getting around MI when it's necessary are by their very exclusion from base language features always arcane, often inefficient, and almost always incomplete. The language-standard D solution is mixins, which I am certain are a bad idea even if they worked better than they do.

I don't have a perfect solution to the problem; there is none because it's asking two objects to exist in one space and we need to resolve that. But given that multiple inheritance is sometimes necessary, waving it off as "too complex" or "bad form" is impractical; like any type of suppression, that only leads to cracking and nasty leaks (mixins).

I have an idea. Let's try making this assertion: no type will have in its inheritance tree another type multiple times without being an abuse of multiple inheritance. Given this statement, can anyone find an exception, where MI is not just being used to alias an object to multiple types (and would therefore be better handled with a field)? Because if that statement is true, then the biggest problem in multiple inheritance can be ignored because all non-abusive usages of it would be resolvable into a clean array, no more difficult than single inheritance, except that methods that exist in more than one separate hierarchies should disappear without manual overloading. What I mean is:

    class A
    {
        void foo (int);
    }

    class B
    {
        void foo (float);
    }

    class C : A, B
    {
        void bar () { foo (16); } // Invalid because foo exists in both A and B. If we overloaded them, it would be like selecting between two functions from different modules. So for language consistency's sake, foo is not visible here.
    }

    class D : A, B
    {
        void foo (int x) { A.foo (x); }
        void foo (float x) { B.foo (x); }
        void bar () { foo (16); } // But this is okay.
    }

Or better yet:

    class E : A, B
    {
        alias A.foo;
        alias B.foo;
    }

The key is in not disallowing something just because it's difficult, but because it's wrong, always wrong, negative index on an array wrong. I'm inclined to think my assertion must be correct, but I can't quite discern the law ruling it. One way in which it is ALWAYS correct is with patterns like this:

    interface I
    {
        void foo ();
    }

    class A : I
    {
        void foo () { ... }
    }

    class B : I
    {
        void foo () { ... }
    }

    class C : A, B
    {
    }

This is because A and B's implementation of foo must be different, so the user is literally asking two objects to exist in one point at the same time, which is not logical. The inheritor cannot confidently select between them with any consistency (which is the fallback MI languages use if they are intelligent enough to notice the problem at all), because he cannot consistently know what either of those methods do (if the method's changed over time, or is written by another maintainer). Since this is an unsolvable ambiguity, it must be wrong to do this.

Gregor Richards wrote:
> Addendum:
> Incidentally, it's usually considered bad form to use multiple
> inheritance in C++.

Yeah, but C++ programmers are programming in C++. The hell do they know about good language practices? ;-)
December 02, 2006
Burton Radons wrote:
> As an aside to Walter, I stumbled on a solution to the multiple vtable problem a couple years ago. Instead of putting the vtable in the object, you put it in the reference, like with a delegate. Then if an object is posing as a base type, you just select an appropriate vtable and offset the pointer.

That is a good idea.
December 02, 2006
Walter Bright wrote:
> Burton Radons wrote:
>> As an aside to Walter, I stumbled on a solution to the multiple vtable problem a couple years ago. Instead of putting the vtable in the object, you put it in the reference, like with a delegate. Then if an object is posing as a base type, you just select an appropriate vtable and offset the pointer.
> 
> That is a good idea.

That is how I think interfaces should work.

It has a whole host of cool implications: interfaces from structs, functions, on-the-fly literals or wherever delegates can be done. Make a .tupleof for interfaces, and a template could even build one.

(I'll get off of my soap box now) <g>
« First   ‹ Prev
1 2