Jump to page: 1 2
Thread overview
Making inheritance less tedious
Feb 26, 2007
Kristian Kilpi
Feb 26, 2007
janderson
Feb 27, 2007
Kevin Bealer
Feb 27, 2007
Bill Baxter
Feb 27, 2007
Kristian Kilpi
Feb 27, 2007
Kevin Bealer
Mar 01, 2007
janderson
Mar 02, 2007
janderson
Mar 02, 2007
Kristian Kilpi
Feb 27, 2007
Sean Kelly
Feb 27, 2007
Michiel
Feb 27, 2007
Sean Kelly
Feb 27, 2007
Bill Baxter
Feb 27, 2007
Michiel
Mar 09, 2007
Kristian Kilpi
February 26, 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 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.D&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);
February 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.D&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
February 27, 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.

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
February 27, 2007
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
February 27, 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, 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

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 pull needed constructors from the base class to the derived class. But, as you mentioned, that can be problematic. I usually need to reimplement all the constructors or to inherit them from the base class as such. So, for me 'all or nothing' approach would be sufficient.
February 27, 2007
== 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.
> 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

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
February 27, 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
February 27, 2007
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
February 27, 2007
Michiel wrote:
> 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.

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
February 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.  :-)
« First   ‹ Prev
1 2