www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Challenge: solve this multiple inheritance problem in your favorite

reply mw <mingwu gmail.com> writes:
Problem:

Suppose a person who has both US & UK residence, travel to Paris, 
and feel ill need to withdraw some money and see a doctor:

1) the person can only have 1 (one) name
2) the person has 3 addresses: one in US, one in UK, and a temp 
hotel address in Paris
3) the person's bank account that can only be read by the bank
4) the person's health info that can only be read by doctor

I will show the Eiffel program, with the compiler ensures all 
these constraints.

First, let me show your the program running result:
--------------------------------------------------------------------------------------

$ ./app
A person have only *one* name
My hotel in Paris
US addr: London
UK addr: NewYork
bank_acct: only view-able by bank
health_info: only view-able by doctor

--------------------------------------------------------------------------------------


This is the multiple inheritance implementation in Eiffel: with 
some comments

--------------------------------------------------------------------------------------
class PERSON

feature {ANY}   -- ANY, basically means `public` in 

    name: STRING is do Result := "A person have only *one* name"  
end
    addr: STRING is do Result := "A person can have *multi*.addr" 
end

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

feature {DOCTOR}
    health_info: STRING is do Result := "health_info: only 
view-able by doctor" end

end

--------------------------------------------------------------------------------------
class UK_RESIDENT
inherit PERSON redefine addr end       -- redefine == override in 

feature {ANY}
    addr: STRING is do Result := "London" end
end

--------------------------------------------------------------------------------------
class US_RESIDENT
inherit PERSON redefine addr end       -- redefine == override in 

feature {ANY}
    addr: STRING is do Result := "NewYork" end
end

--------------------------------------------------------------------------------------
class VISITOR
inherit             -- Note: inherit PERSON 3 times! but treat 
each 3 address individually
         UK_RESIDENT rename addr as uk_addr end
         US_RESIDENT rename addr as us_addr end
         PERSON      redefine addr select addr end


    make

feature {ANY}
    make is do end

    addr: STRING is
       do
          Result := "My hotel in Paris"
       end
end

--------------------------------------------------------------------------------------
class BANK

create {ANY}
    make

feature
   make is do end

   read_bank_acct(u: PERSON) is
     do
       io.put_string(u.bank_acct    + "%N")
     --io.put_string(u.health_info  + "%N") -- ****** Fatal Error: 
This feature is only exported to {DOCTOR}.
     end
end

--------------------------------------------------------------------------------------
class DOCTOR

create {ANY}
    make

feature
   make is do end

   read_health_info(u: PERSON) is
     do
     --io.put_string(u.bank_acct    + "%N") -- ****** Fatal Error: 
This feature is only exported to {BANK}.
       io.put_string(u.health_info  + "%N")
     end
end

--------------------------------------------------------------------------------------
-- to build: compile  app.e -o app
class APP

create {ANY}
    main

feature {ANY}
    visitor: VISITOR
    bank: BANK
    doctor: DOCTOR

    print_uk_addr(u: VISITOR) is do io.put_string("US addr: " + 
u.uk_addr + "%N") end
    print_us_addr(u: VISITOR) is do io.put_string("UK addr: " + 
u.us_addr + "%N") end

    main is
       do
          create bank.make
          create doctor.make
          create visitor.make

          io.put_string(visitor.name + "%N")
          io.put_string(visitor.addr + "%N")
          print_uk_addr(visitor)
          print_us_addr(visitor)

          bank.read_bank_acct(visitor)
          doctor.read_health_info(visitor)
       end

end
--------------------------------------------------------------------------------------

Note: the `--` commented out line, followed by the compiler error 
message: "Fatal Error ...."


(I'm a bit busy today, stop here. I will continue tomorrow).

Feel free to add your implementation in your favorite programming 
language.
Jun 04
next sibling parent mw <mingwu gmail.com> writes:
On Thursday, 4 June 2020 at 07:11:26 UTC, mw wrote:
 Note: the `--` commented out line, followed by the compiler 
 error message: "Fatal Error ...."
Note: the `--` commented out line in class BANK and DOCTOR, if un-commented, will get the compiler error message "Fatal Error ...."
Jun 04
prev sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Thursday, 4 June 2020 at 07:11:26 UTC, mw wrote:
 Problem:

 Suppose a person who has both US & UK residence, travel to 
 Paris, and feel ill need to withdraw some money and see a 
 doctor:

 1) the person can only have 1 (one) name
 2) the person has 3 addresses: one in US, one in UK, and a temp 
 hotel address in Paris
 3) the person's bank account that can only be read by the bank
 4) the person's health info that can only be read by doctor
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: - What happens when the person buys a holiday home in Italy? - Do we need to define a separate inheritance tree for all possible combinations? Now, for showing off some of Eiffel's features, there's some good stuff here - the feature export system is kinda 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); } -- Simen
Jun 04
parent reply mw <mingwu gmail.com> writes:
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
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 :-)
Jun 04
parent reply Jacob Carlborg <doob me.com> writes:
On 2020-06-04 19:25, mw wrote:

 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);
    }
 }
 ----------------------------------------------------------------------
It's already possible to do that today: class Person : NameI, AddrI { mixin NameT!Person Name; mixin AddrT!Person Addr; bool equals(Person other) { return Name.equals(other) && Addr.equals(other); } } -- /Jacob Carlborg
Jun 04
parent mw <mingwu gmail.com> writes:
On Friday, 5 June 2020 at 06:40:06 UTC, Jacob Carlborg wrote:
 It's already possible to do that today:

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

   bool equals(Person other) {
     return Name.equals(other) &&
            Addr.equals(other);
   }
 }
Thank you for letting me know. This alleviates the name clashing problem, but didn't completely solve it, this renaming is still coarse-grained all-or-none, e.g: class Visitor { mixin UKResident UKR; mixin USResident USR; } all the attributes in UKResident.<attr> is rename to UKR.<attr> and all the attributes in USResident.<attr> is rename to USR.<attr> While we want to achieve: UKR.name === USR.name (have the same storage) UKR.addr !=== USR.addr (have different storage) i.e. fine-grained control on each mixin's attribute to be either joined or separated.
Jun 05