Challenge: solve this multiple inheritance problem in your favorite language

mw mingwu at gmail.com
Thu Jun 4 17:25:38 UTC 2020


On Thursday, 4 June 2020 at 09:37:38 UTC, Simen Kjærås wrote:
> This is all fairly reasonable, but why use multiple 
> inheritance? I mean, it might be the logical way to do it in 
> Eiffel, but in D that's just not the right way.
>
> For that matter, it reads as a very artificial situation:

The Diamond problem is a well-known issue, e.g. the majority of 
the wiki article on multiple inheritance is dedicated to it: 
https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem

I don't think my example is more artificial for the diamond 
problem than the dining-philosophers problem for concurrent 
programming (why not the philosophers simply ask the waitress for 
more forks? :-)

Anyway, you can try to solve the Object => (Button, Clickable) 
=>=> Button.equals() problem on the wiki page if you think it's 
less artificial; I won't argue in this direction any further.

> - What happens when the person buys a holiday home in Italy?
> - Do we need to define a separate inheritance tree for all 
> possible combinations?

(As a side note: just because Eiffel solved the Diamond problem 
so successfully, in Eiffel programmers are *encouraged* to use 
(abuse :-) multiple inheritance as much as they can -- taking 
into account it's a pure OO language. This is in contrast in 
other languages, multiple inheritance usage is discouraged.)


Although D intendeds to have single-inheritance with multiple 
interfaces, but the multiple inheritance problems have crept into 
D already, because of the introduction of `mixin` and `alias xxx 
this`. As a simple example, when a class has multiple interfaces 
and multiple mixins, we may run into issues:

$ cat multi.d
----------------------------------------------------------------------
interface NameI { string name(); }
interface AddrI { string addr(); }

mixin template NameT(T) {
   string name() {return "name";}
   bool equals(T other) {
     return this.name() == other.name();
   }
}

mixin template AddrT(T) {
   string addr() {return "addr";}
   bool equals(T other) {
     return this.addr() == other.addr();
   }
}

class Person : NameI, AddrI {
   mixin NameT!Person;
   mixin AddrT!Person;
}

void main() {
   Person p1 = new Person();
   Person p2 = new Person();

   p1.equals(p2); // Error: function 
multi.Person.AddrT!(Person).equals at multi.d(13) conflicts with 
function multi.Person.NameT!(Person).equals at multi.d(6)
}
----------------------------------------------------------------------
(BTW, without the last line, the program compiles.)

And ideally, the function NameT.equals() and AddrT.equals() 
should be reused, and be combined to define a new Person.equals().

 From C++'s way of thinking, multiple inheritance (read D's mixin) 
is all-or-none: either *all* the attributes of common ancestor 
are separate copy, or be joined as one copy (called `virtual 
inheritance`); but this didn't fully solve the problem.

So Walter said:
https://forum.dlang.org/post/rb4seo$bfm$1@digitalmars.com
"""
The trouble was, it was inserted without realizing it was 
multiple inheritance, meaning its behaviors are ad-hoc and don't 
make a whole lot of sense when examined carefully.
"""

Actually, I think it still solvable: by dealing with each 
attribute from the parent class individually (instead of as a 
whole), just follow Eiffel's method, adding language mechanism 
(esp `rename`) to allow programmer to decide how to resolve the 
conflict.

I'd imaging something like this:

----------------------------------------------------------------------
class Person : NameI, AddrI {
   mixin NameT!Person rename equals as name_equals;
   mixin AddrT!Person rename equals as addr_equals;

   bool equals(Person other) {
     return this.name_equals(other) &&
            this.addr_equlas(other);
   }
}
----------------------------------------------------------------------


> Now, for showing off some of Eiffel's features, there's some 
> good stuff here - the feature export system is kinda

Access-control is the same: in C++/C#/Java world, it's public (to 
the world), protected (to the world), private (to the world), 
coarse-grained -- even C++'s `friend` can access the declaring 
class' *all* attributes; v.s Eiffel's access-control: just name 
the outside class in an access list {Bank, WillExecutor}, it's 
fine-grained.


> interesting, and doesn't really have a good analog in D, but 
> may be approximated with non-creatable types:
>
> module person;
> import bank;
> class Person {
>     string bankingDetails(Bank.Token) {
>         return "Account number readable only by Bank";
>     }
> }
>
> module bank;
> import person;
> class Bank {
>     struct Token {
>         @disable this();
>         private this(int i) {}
>     }
>     string personBankingDetails(Person person) {
>         return person.bankingDetails(Token(0));
>     }
> }
>
> module test;
> import bank;
> import person;
> unittest {
>     Person p = new Person();
>     Bank b = new Bank();
>
>     // Won't compile - only a Bank can create a Token
>     //p.bankingDetails();
>
>     // Works fine
>     b.personBankingDetails(p);
> }

ok, essentially one line change here:

------------------------
feature {BANK, WillExecutor}
    bank_acct: STRING is do Result := "bank_acct: only view-able 
by bank" end
------------------------

Now, it's your turn now :-)



More information about the Digitalmars-d mailing list