Thread overview
Challenge: solve this multiple inheritance problem in your favorite language
Jun 04, 2020
mw
Jun 04, 2020
mw
Jun 04, 2020
Simen Kjærås
Jun 04, 2020
mw
Jun 05, 2020
Jacob Carlborg
Jun 05, 2020
mw
Sep 28, 2020
IGotD-
Sep 28, 2020
mw
June 04, 2020
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:
--------------------------------------------------------------------------------------
$ compile  app.e -o app  # Eiffel compiler command
$ ./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 C/C++/C#/D/Java world
   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 C#/D/Java
feature {ANY}
   addr: STRING is do Result := "London" end
end

--------------------------------------------------------------------------------------
class US_RESIDENT
inherit PERSON redefine addr end       -- redefine == override in C#/D/Java
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

create {ANY}   -- means c-tor in C++/C#/D/Java
   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.
June 04, 2020
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 ...."

June 04, 2020
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
June 04, 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 :-)

June 05, 2020
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
June 05, 2020
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.


September 28, 2020
FYI, I just uploaded SmartEiffel (open source) 1.1 compiler, and my previous visitor example to:

https://github.com/mingwugmail/dlang_tour/tree/master/eiffel

You can play with it.


Eiffel is a compiled static typed language. All the class's memory layout is know at compile time (as it's fully specified by the programmer in the source code).

BTW, many (if not all) Eiffel compilers actually compile Eiffel program to C (as target language).

And the generated C code of my example is:

https://github.com/mingwugmail/dlang_tour/blob/master/eiffel/visitor/app1.c

You can study it.

September 28, 2020
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);
>   }
> }

That was important information for as it means that some symbols collisions can be avoided. I didn't know that mixins had a scope. This makes the mixin templates more useful.