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