www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Making inheritance less tedious

reply "Kristian Kilpi" <kjkilpi gmail.com> writes:
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
next sibling parent janderson <askme me.com> writes:
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
prev sibling next sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
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. Something like this syntax might be in line with D's solution for #2. 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
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Kevin Bealer wrote:
 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.

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 = 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? } --bb
Feb 26 2007
next sibling parent "Kristian Kilpi" <kjkilpi gmail.com> writes:
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,  =



 unnecessarily tedious).
 (I leave interfaces out of this discussion.) 'Problems', IMHO, with =



 are:

 1) You cannot inherit constructors.



 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'=

 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?
 }

 --bb

In 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
prev sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Bill Baxter (dnewsgroup billbaxter.com)'s article
 Kevin Bealer wrote:
 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.

As for (1), I agree and this is something that annoys me in C++ too.

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? } --bb

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 }; Kevin
Feb 27 2007
parent reply janderson <askme me.com> writes:
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) {}
 };

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
parent reply janderson <askme me.com> writes:
janderson wrote:
 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) {}
 };

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

I should clarify, I'm talking about the template mixins, not the ones that take a string. -Joel
Mar 01 2007
parent "Kristian Kilpi" <kjkilpi gmail.com> writes:
On Fri, 02 Mar 2007 09:26:21 +0200, janderson <askme me.com> wrote:

 janderson wrote:
 Kevin Bealer wrote:
 I've often thought that in C++ I could achieve neat results by copyi=



 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.  =



 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) {}
 };

This is where I think well designed mixins really start showing thei=


 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=


 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.
  =3DJoel

I should clarify, I'm talking about the template mixins, not the ones =

 that take a string.

 -Joel

Yes, 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
prev sibling next sibling parent reply Sean Kelly <sean f4.ca> writes:
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
parent reply Michiel <nomail please.com> writes:
Sean Kelly wrote:

 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.

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. -- Michiel
Feb 27 2007
next sibling parent Sean Kelly <sean f4.ca> writes:
Michiel wrote:
 Sean Kelly wrote:
 
 1) You cannot inherit constructors.

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.

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.

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.
 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
prev sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
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
parent Michiel <nomail please.com> writes:
Bill Baxter 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.

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. -- Michiel
Feb 27 2007
prev sibling parent "Kristian Kilpi" <kjkilpi gmail.com> writes:
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.

 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=

 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