digitalmars.D - Challenge: solve this multiple inheritance problem in your favorite
- mw (126/126) Jun 04 2020 Problem:
- mw (4/6) Jun 04 2020 Note: the `--` commented out line in class BANK and DOCTOR, if
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (43/52) Jun 04 2020 This is all fairly reasonable, but why use multiple inheritance?
- mw (92/132) Jun 04 2020 The Diamond problem is a well-known issue, e.g. the majority of
- Jacob Carlborg (12/30) Jun 04 2020 It's already possible to do that today:
- mw (12/12) Sep 28 2020 FYI, I just uploaded SmartEiffel (open source) 1.1 compiler, and
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 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 ...."
Jun 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 doctorThis 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 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 kindathe 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 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
Jun 04 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.
Jun 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); } }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.
Sep 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.
Sep 28 2020