digitalmars.D - Making inheritance less tedious
- Kristian Kilpi (76/76) Feb 26 2007 I think class inheritance is a bit(?) tedious sometimes (that is, =
- janderson (4/97) Feb 26 2007 I agree about it being tedious. One ->slight<- improvement to your
- Kevin Bealer (11/19) Feb 26 2007 I think 2) is considered a feature and done deliberately to prevent
- Bill Baxter (35/46) Feb 26 2007 So what would be the effect of calling a base class constructor?
- Kristian Kilpi (18/63) Feb 27 2007 it =
- Kevin Bealer (41/87) Feb 27 2007 My proposal is that you need to use "alias" or something like it to inhe...
- janderson (8/19) Feb 28 2007 [snip]
- janderson (4/28) Mar 01 2007 I should clarify, I'm talking about the template mixins, not the ones
- Kristian Kilpi (20/46) Mar 02 2007 s =
- Sean Kelly (36/42) Feb 27 2007 Consider:
- Michiel (19/29) Feb 27 2007 I have to disagree here (partially).
- Sean Kelly (10/34) Feb 27 2007 Yeah, it was a bad example. I was really thinking more along the lines
- Bill Baxter (9/16) Feb 27 2007 Isn't that what interfaces and object composition are for?
- Michiel (15/27) Feb 27 2007 Object composition would indicate a 'has-a' relationship. Both normal
- Kristian Kilpi (11/37) Mar 09 2007 =
I think class inheritance is a bit(?) tedious sometimes (that is, = unnecessarily tedious). (I leave interfaces out of this discussion.) 'Problems', IMHO, with it a= re: 1) You cannot inherit constructors. 2) A function will hide inherited functions having the same name. 3) The syntax for calling a super function of the base class is redundan= t = and (could be) tedious. For the case 2 there is an 'alias hack' (e.g. 'alias Base.foo foo;'), bu= t = it's redundant and ugly. Recently this issue was discussed in the thread 'aliasing base methods' = = started by Frank Benoit (http://www.digitalmars.com/webnews/newsgroups.php?art_group=3Ddigitalma= rs.D&article_id=3D49572), = so I don't talk about it here. There should be some way to 'break' the case 1. For example, the followi= ng = is laborious and error-prone: class CheckBox { this(); this(string label); this(Icon icon, string label =3D null); void draw(); } class MyCheckBox : CheckBox { this() { super(); } this(string label) { super(label); } this(Icon icon, string label =3D null) { super(icon, label); } void draw(); } All I wanted was to reimplement the 'draw()' function. But I had to writ= e = all those redundant constructors also! (Imagine if there were 20 or so constructors in the base class -- yes, = it's possible in some situations.) Surely there should be a simple solution for this? For example, something like this: class MyCheckBox : CheckBox { inherit constructors; void draw(); } Or better yet: class MyCheckBox : inherit CheckBox { void draw(); } What about the case 3? Well, lets have the following member function: foo(int value, int x, int y, bool is_clipped, bool is_inverted, strin= g = label =3D null) { ... super.foo(value, x, y, is_clipped, is_inverted, label); } It would be nice to have the following possible: foo(int value, int x, int y, bool is_clipped, bool is_inverted, strin= g = label =3D null) { ... superfunc(..); //equals to 'super.foo(value, x, y, is_clipped, = is_inverted, label);' } 'superfunc(..)' (or 'superfunc($)' or something) is non-redundant, easy = to = read, and trivial to maintain (because no maintenance is needed! :) ). You can of course define the arguments by yourself if needed: superfunc(value + 1, x, y, false, is_inverted);
Feb 26 2007
Kristian Kilpi wrote:I think class inheritance is a bit(?) tedious sometimes (that is, unnecessarily tedious). (I leave interfaces out of this discussion.) 'Problems', IMHO, with it are: 1) You cannot inherit constructors. 2) A function will hide inherited functions having the same name. 3) The syntax for calling a super function of the base class is redundant and (could be) tedious. For the case 2 there is an 'alias hack' (e.g. 'alias Base.foo foo;'), but it's redundant and ugly. Recently this issue was discussed in the thread 'aliasing base methods' started by Frank Benoit (http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars. &article_id=49572), so I don't talk about it here. There should be some way to 'break' the case 1. For example, the following is laborious and error-prone: class CheckBox { this(); this(string label); this(Icon icon, string label = null); void draw(); } class MyCheckBox : CheckBox { this() { super(); } this(string label) { super(label); } this(Icon icon, string label = null) { super(icon, label); } void draw(); } All I wanted was to reimplement the 'draw()' function. But I had to write all those redundant constructors also! (Imagine if there were 20 or so constructors in the base class -- yes, it's possible in some situations.) Surely there should be a simple solution for this? For example, something like this: class MyCheckBox : CheckBox { inherit constructors; void draw(); } Or better yet: class MyCheckBox : inherit CheckBox { void draw(); } What about the case 3? Well, lets have the following member function: foo(int value, int x, int y, bool is_clipped, bool is_inverted, string label = null) { ... super.foo(value, x, y, is_clipped, is_inverted, label); } It would be nice to have the following possible: foo(int value, int x, int y, bool is_clipped, bool is_inverted, string label = null) { ... superfunc(..); //equals to 'super.foo(value, x, y, is_clipped, is_inverted, label);' } 'superfunc(..)' (or 'superfunc($)' or something) is non-redundant, easy to read, and trivial to maintain (because no maintenance is needed! :) ). You can of course define the arguments by yourself if needed: superfunc(value + 1, x, y, false, is_inverted);I agree about it being tedious. One ->slight<- improvement to your problem is to use more mixins for the boilerplate code. -Joel
Feb 26 2007
Kristian Kilpi wrote:I think class inheritance is a bit(?) tedious sometimes (that is, unnecessarily tedious). (I leave interfaces out of this discussion.) 'Problems', IMHO, with it are: 1) You cannot inherit constructors. 2) A function will hide inherited functions having the same name.I think 2) is considered a feature and done deliberately to prevent certain kinds of errors. I never thought much about the details though. As for (1), I agree and this is something that annoys me in C++ too. class X : Y { alias Y.this this; } Though I imagine the underlying implementation would be different than for a non-constructor method, this syntax looks clear, to me at least. Kevin
Feb 26 2007
Kevin Bealer wrote:Kristian Kilpi wrote:So what would be the effect of calling a base class constructor? I guess it would have to call the base constructor followed by the derived class's default constructor? It seems like that could cause headaches. Maybe that's why C++ doesn't allow it. Especially in D where the default constructor may already call the base constructor. class Base { this(int a, float b) { m_a = a; m_b = b; } int m_a; float m_b; } class Derived : Base { this() { super(2,3.0f); mString = "Happy happy"; } this(int a) { super(a,6.0f); mString = "howdy howdy"; } char[] mString; } void main() { Derived x = new Derived(a,b); // what got called exactly? // and what values do x.m_a and x.m_b have now? } --bbI think class inheritance is a bit(?) tedious sometimes (that is, unnecessarily tedious). (I leave interfaces out of this discussion.) 'Problems', IMHO, with it are: 1) You cannot inherit constructors.As for (1), I agree and this is something that annoys me in C++ too.
Feb 26 2007
On Tue, 27 Feb 2007 08:54:06 +0200, Bill Baxter = <dnewsgroup billbaxter.com> wrote:Kevin Bealer wrote:Kristian Kilpi wrote:I think class inheritance is a bit(?) tedious sometimes (that is, =it =unnecessarily tedious). (I leave interfaces out of this discussion.) 'Problems', IMHO, with =are: 1) You cannot inherit constructors.As for (1), I agree and this is something that annoys me in C++ too.=So what would be the effect of calling a base class constructor? I guess it would have to call the base constructor followed by the =derived class's default constructor? It seems like that could cause headaches. Maybe that's why C++ doesn'=t =allow it. Especially in D where the default constructor may already =call the base constructor. class Base { this(int a, float b) { m_a =3D a; m_b =3D b; } int m_a; float m_b; } class Derived : Base { this() { super(2,3.0f); mString =3D "Happy happy"; } this(int a) { super(a,6.0f); mString =3D "howdy howdy"; } char[] mString; } void main() { Derived x =3D new Derived(a,b); // what got called exactly? // and what values do x.m_a and x.m_b have now? } --bbIn my proposal I intented that you could inherit base class constructor = = only if you didn't define constructors in the derived class. So your = example wouldn't compile because 'Derived' don't have 'this(int, float)'= . However, if a selective inheritance would be possible, then you could pu= ll = needed constructors from the base class to the derived class. But, as yo= u = mentioned, that can be problematic. I usually need to reimplement all th= e = constructors or to inherit them from the base class as such. So, for me = = 'all or nothing' approach would be sufficient.
Feb 27 2007
== Quote from Bill Baxter (dnewsgroup billbaxter.com)'s articleKevin Bealer wrote:My proposal is that you need to use "alias" or something like it to inherit the constructors. You clipped this part out but this is the syntax: class X : Y { alias Y.this this; } So for my proposal, your example would just be a syntax error, unless you add the 'alias' statement shown here. If you *did* add "alias Base.this this", then D would add this definition to Derived: class Derived : Base { ... this(int a, int b) { super(a, b); } ... } It would keep your other constructors and methods just as they are. If you want to make sure the mString is set, then you would not want to use this feature. I've often thought that in C++ I could achieve neat results by copying the vector, string, and map classes and adding my own functionality, but there is the annoyance of duplicating the half dozen or so string constructors. The simplest c++ syntax I have found is this (not tested): class MyString : string { public: template<A> MyString(A x) : string(x) {} template<A,B> MyString(A x, B y) : string(x,y) {} template<A,B,C> MyString(A x, B y, C z) : string(x,y,z) {} }; I think of this as a 'forwarding' technique. It's covers all methods with a certain number of parameters. I don't have to worry about (char*,char*) and (iterator,iterator) or whatever because they are both covered by the above 2-input syntax. (I don't know how well this works for in,out,inout, or their C++ equivalents.) However, since the only thing I'm doing in that code is duplicating the string() methods, it would be neat if I could just do this: class MyString : string { public: using string::string; // or whatever }; KevinKristian Kilpi wrote:So what would be the effect of calling a base class constructor? I guess it would have to call the base constructor followed by the derived class's default constructor? It seems like that could cause headaches. Maybe that's why C++ doesn't allow it. Especially in D where the default constructor may already call the base constructor. class Base { this(int a, float b) { m_a = a; m_b = b; } int m_a; float m_b; } class Derived : Base { this() { super(2,3.0f); mString = "Happy happy"; } this(int a) { super(a,6.0f); mString = "howdy howdy"; } char[] mString; } void main() { Derived x = new Derived(a,b); // what got called exactly? // and what values do x.m_a and x.m_b have now? } --bbI think class inheritance is a bit(?) tedious sometimes (that is, unnecessarily tedious). (I leave interfaces out of this discussion.) 'Problems', IMHO, with it are: 1) You cannot inherit constructors.As for (1), I agree and this is something that annoys me in C++ too.
Feb 27 2007
Kevin Bealer wrote:I've often thought that in C++ I could achieve neat results by copying the vector, string, and map classes and adding my own functionality, but there is the annoyance of duplicating the half dozen or so string constructors. The simplest c++ syntax I have found is this (not tested): class MyString : string { public: template<A> MyString(A x) : string(x) {} template<A,B> MyString(A x, B y) : string(x,y) {} template<A,B,C> MyString(A x, B y, C z) : string(x,y,z) {} };[snip] This is where I think well designed mixins really start showing their power. The problem in C++ is that the base classes are not written in the mixin design pattern, probably because its yet another level of C++ complexity to add. In D mixins are easy, so theres is really no excuse. Having said that I don't think they solve this problem entirely. =Joel
Feb 28 2007
janderson wrote:Kevin Bealer wrote:I should clarify, I'm talking about the template mixins, not the ones that take a string. -JoelI've often thought that in C++ I could achieve neat results by copying the vector, string, and map classes and adding my own functionality, but there is the annoyance of duplicating the half dozen or so string constructors. The simplest c++ syntax I have found is this (not tested): class MyString : string { public: template<A> MyString(A x) : string(x) {} template<A,B> MyString(A x, B y) : string(x,y) {} template<A,B,C> MyString(A x, B y, C z) : string(x,y,z) {} };[snip] This is where I think well designed mixins really start showing their power. The problem in C++ is that the base classes are not written in the mixin design pattern, probably because its yet another level of C++ complexity to add. In D mixins are easy, so theres is really no excuse. Having said that I don't think they solve this problem entirely. =Joel
Mar 01 2007
On Fri, 02 Mar 2007 09:26:21 +0200, janderson <askme me.com> wrote:janderson wrote:ng =Kevin Bealer wrote:I've often thought that in C++ I could achieve neat results by copyi=s =the vector, string, and map classes and adding my own functionality, but there i==the annoyance of duplicating the half dozen or so string constructors. =r =The simplest c++ syntax I have found is this (not tested): class MyString : string { public: template<A> MyString(A x) : string(x) {} template<A,B> MyString(A x, B y) : string(x,y) {} template<A,B,C> MyString(A x, B y, C z) : string(x,y,z) {} };[snip] This is where I think well designed mixins really start showing thei=n =power. The problem in C++ is that the base classes are not written i=++ =the mixin design pattern, probably because its yet another level of C=se.complexity to add. In D mixins are easy, so theres is really no excu==Having said that I don't think they solve this problem entirely. =3DJoelI should clarify, I'm talking about the template mixins, not the ones =that take a string. -JoelYes, mixins are useful in some situation, as you said. However, if I nee= d = to (quickly) change the behavior of a class by overriding a function or = = two, mixins don't help: I have to write (usually) the constructors anywa= y. = If I'm writing a library, I could provide mixins for easier inheritance.= = That's just a lot of unnecessary work and redundant code, and they make = = the library harder to maintain.
Mar 02 2007
Kristian Kilpi wrote:I think class inheritance is a bit(?) tedious sometimes (that is, unnecessarily tedious). (I leave interfaces out of this discussion.) 'Problems', IMHO, with it are: 1) You cannot inherit constructors.Consider: module A; class Pet { this( char[] name ) { m_name = name; } private char[] m_name; } module B; import A; class Puppy : Pet {} class Aibo : Pet {} /* arguably a pet */ Assuming the ctor is inherited, then so far so good. Puppies and Aibos both have names. Now let's say the base class has some new functionality added: class Pet { this( char[] name, Food eats ) { m_name = name; } private char[] m_name; private Food m_eats; } The new inherited behavior makes sense for Puppies, but not necessarily for Aibos. One might argue that a refactoring of the hierarchy is a good idea here, or that Aibos aren't actually pets, but I think it's reasonable to assume that because inheritance suggests specialization, it may occasionally be desirable to place constraints on inherited attributes. Forcing the programmer to implement ctors for a class is a one simple way to avoid subtly breaking behavior from changes up the inheritance tree. Sean
Feb 27 2007
Sean Kelly wrote:I have to disagree here (partially). If all Pets eat (specified by putting this behaviour/data in the Pet class) and an Aibo is a Pet (specified by inheriting from Pet, creating an is-a relationship), then Aibo's should also eat. If Aibo's do not eat, then either an Aibo is not a Pet, or Pets in general don't eat. Ok, ok. I realise that this may be nothing more than my personal programming philosophy. But it makes sense. If you are allowed to randomly disregard parts of the public interface from a base class, then the is-a relationship no longer really holds, and you couldn't give an Aibo to a function that asks for a Pet (and might try to feed it). However, I understand your point. If a base class is changed, and you have no source-access to it, you still don't want to entirely break your subclass away from it. Maybe another type of relationship should be formally introduced for such situations. 'Is-kinda-like', or 'is-mutation-of' or something, which would allow suppressing parts of the public interface. -- Michiel1) You cannot inherit constructors.<SNIP> The new inherited behavior makes sense for Puppies, but not necessarily for Aibos. One might argue that a refactoring of the hierarchy is a good idea here, or that Aibos aren't actually pets, but I think it's reasonable to assume that because inheritance suggests specialization, it may occasionally be desirable to place constraints on inherited attributes.
Feb 27 2007
Michiel wrote:Sean Kelly wrote:Yeah, it was a bad example. I was really thinking more along the lines of constraints rather than hiding behavior altogether. Maybe Dogs eat beef but snakes eat mice, but the property is stored in Animal because all animals eat.I have to disagree here (partially). If all Pets eat (specified by putting this behaviour/data in the Pet class) and an Aibo is a Pet (specified by inheriting from Pet, creating an is-a relationship), then Aibo's should also eat. If Aibo's do not eat, then either an Aibo is not a Pet, or Pets in general don't eat.1) You cannot inherit constructors.<SNIP> The new inherited behavior makes sense for Puppies, but not necessarily for Aibos. One might argue that a refactoring of the hierarchy is a good idea here, or that Aibos aren't actually pets, but I think it's reasonable to assume that because inheritance suggests specialization, it may occasionally be desirable to place constraints on inherited attributes.However, I understand your point. If a base class is changed, and you have no source-access to it, you still don't want to entirely break your subclass away from it. Maybe another type of relationship should be formally introduced for such situations. 'Is-kinda-like', or 'is-mutation-of' or something, which would allow suppressing parts of the public interface.This is where I was getting at with refactoring the hierarchy, but that isn't always feasible. I suppose an alternative would be to do as you say and specify exactly what traits are inherited, but I'm not sure if the added complexity is worth it. Sean
Feb 27 2007
Michiel wrote:However, I understand your point. If a base class is changed, and you have no source-access to it, you still don't want to entirely break your subclass away from it. Maybe another type of relationship should be formally introduced for such situations. 'Is-kinda-like', or 'is-mutation-of' or something, which would allow suppressing parts of the public interface.Isn't that what interfaces and object composition are for? If what you have isn't is-A, then like you said inheritance is probably not the relationship you're after. Create a ThingsPeopleHaveIrrationalAttatchmentsTo interface and have both Pet and Aibo implement it. Aibo might implement it internally using a Pet member. In C++ you also have the option of private inheritance. Does D have that? Anyway, Aibo's eat electricity, so there's no problem here to begin with. :-)
Feb 27 2007
Bill Baxter wrote:Object composition would indicate a 'has-a' relationship. Both normal inheritance AND interfaces indicate 'is-a' relationships. I like your solution. But if an 'is-kinda-like' relationship could formally be created, that would be even better. If Aibo is-kinda-like Pet (with the food-interface removed), then: * You could not pass an Aibo to a function that asks for a Pet. You would simply use Pet for the implementation only. * Maybe a smart compiler could recognize that the private member 'food' is not used by any inherited public function and disregard it when allocating memory. * Code would become more readable and maintainable. There could be other advantages. -- MichielHowever, I understand your point. If a base class is changed, and you have no source-access to it, you still don't want to entirely break your subclass away from it. Maybe another type of relationship should be formally introduced for such situations. 'Is-kinda-like', or 'is-mutation-of' or something, which would allow suppressing parts of the public interface.Isn't that what interfaces and object composition are for? If what you have isn't is-A, then like you said inheritance is probably not the relationship you're after. Create a ThingsPeopleHaveIrrationalAttatchmentsTo interface and have both Pet and Aibo implement it. Aibo might implement it internally using a Pet member.
Feb 27 2007
On Mon, 26 Feb 2007 15:31:29 +0200, Kristian Kilpi <kjkilpi gmail.com> = wrote:I think class inheritance is a bit(?) tedious sometimes (that is, =unnecessarily tedious). (I leave interfaces out of this discussion.) 'Problems', IMHO, with it==are: 1) You cannot inherit constructors. 2) A function will hide inherited functions having the same name. 3) The syntax for calling a super function of the base class is =redundant and (could be) tedious.[snip]What about the case 3? Well, lets have the following member function: foo(int value, int x, int y, bool is_clipped, bool is_inverted, =string label =3D null) { ... super.foo(value, x, y, is_clipped, is_inverted, label); } It would be nice to have the following possible: foo(int value, int x, int y, bool is_clipped, bool is_inverted, =string label =3D null) { ... superfunc(..); //equals to 'super.foo(value, x, y, is_clipped, =is_inverted, label);' } 'superfunc(..)' (or 'superfunc($)' or something) is non-redundant, eas=y =to read, and trivial to maintain (because no maintenance is needed! :) ). You can of course define the arguments by yourself if needed: superfunc(value + 1, x, y, false, is_inverted);What, nobody voted for the case 3? I'm stunned. ;) To me having 'superfunc' would be about as nice as having 'this' for = ctors/dtors... Lets put it this way: how many C++ person would switch back to use the = class name in the construtors and destructors instead of 'this'?
Mar 09 2007