Thread overview
Extending DBC (was: The Alias peek-a-boo Game)
Jul 29, 2004
stonecobra
Re: New proposal (was: Extending DBC (was: The Alias peek-a-boo Game))
Jul 29, 2004
Regan Heath
Jul 29, 2004
Regan Heath
Jul 29, 2004
Regan Heath
Re: New proposal
Jul 29, 2004
Derek Parnell
Jul 29, 2004
stonecobra
Jul 29, 2004
stonecobra
Jul 29, 2004
Regan Heath
Jul 29, 2004
stonecobra
Jul 29, 2004
Regan Heath
July 29, 2004
So, this whole thread got me thinking.  D is not about being like Java or C++, it really is about being better than either of them.

To that end, the following is my proposal to a) extend Design by Contract, and b) eliminated the (perceived to some) warts of the alias peek-a-boo game.

Just as a function has in, out and body blocks, let's suppose that a class can have a body block (as it does now), and/or an 'exclude' block.  This exclude block would look something like this:

Given a superclass UnicodeWriter:

class UnicodeWriter
{
  void writeChar(char c)
  { ... }

  void writeChar(wchar w)
  { ... }

  void writeChar(dchar d)
  { ... }

  void toString()
  { ... }

  void printDebugInfo()
  { ... }
}

and now, for corporate reasons, all classes _cannot_ use chars internally, and when using wchars, more stringent checks must be applied.  Also, the printDebugInfo method has been determined to be too expensive and not useful, so it should be removed.  Oh yeah, we can't just remove printDebugInfo(), since we didn't write UnicodeWriter, so we don't have the source, because Arcane Jill has gone commercial with the Deimos Unicode Library :)  So we write a subclass, CorporateUnicodeWriter:

class CorporateUnicodeWriter : UnicodeWriter
excludes
{
  void writeChar(char c);
  void printDebugInfo();
}
body
{
  void writeChar(wchar w)
  in { ... }
  body { ... }
  out { ... }
}

Now, let's pretend that this existed in D, along with scoping rules that were more inclusive.  What we have is the ability to constrain the contract of a subclass to exactly what we want, whether we own the superclass or not.  The model needs to be inclusive for this to work, but then:
a) all concerns with alias are addressed
b) D as a language becomes more symmetrical in the DBC realm
c) extending design by contract is cool :)
d) we are better than Java AND C++ because we can remove contracts
e) constructors, etc might also be implicitly passed down and deleted where necessary.


Whaddayathink?

Scott Sanders
July 29, 2004
On Wed, 28 Jul 2004 21:04:58 -0700, stonecobra <scott@stonecobra.com> wrote:
> So, this whole thread got me thinking.  D is not about being like Java or C++, it really is about being better than either of them.
>
> To that end, the following is my proposal to a) extend Design by Contract, and b) eliminated the (perceived to some) warts of the alias peek-a-boo game.
>
> Just as a function has in, out and body blocks, let's suppose that a class can have a body block (as it does now), and/or an 'exclude' block.   This exclude block would look something like this:
>
> Given a superclass UnicodeWriter:
>
> class UnicodeWriter
> {
>    void writeChar(char c)
>    { ... }
>
>    void writeChar(wchar w)
>    { ... }
>
>    void writeChar(dchar d)
>    { ... }
>
>    void toString()
>    { ... }
>
>    void printDebugInfo()
>    { ... }
> }
>
> and now, for corporate reasons, all classes _cannot_ use chars internally, and when using wchars, more stringent checks must be applied.  Also, the printDebugInfo method has been determined to be too expensive and not useful, so it should be removed.  Oh yeah, we can't just remove printDebugInfo(), since we didn't write UnicodeWriter, so we don't have the source, because Arcane Jill has gone commercial with the Deimos Unicode Library :)  So we write a subclass, CorporateUnicodeWriter:
>
> class CorporateUnicodeWriter : UnicodeWriter
> excludes
> {
>    void writeChar(char c);
>    void printDebugInfo();
> }
> body
> {
>    void writeChar(wchar w)
>    in { ... }
>    body { ... }
>    out { ... }
> }
>
> Now, let's pretend that this existed in D, along with scoping rules that were more inclusive.  What we have is the ability to constrain the contract of a subclass to exactly what we want, whether we own the superclass or not.  The model needs to be inclusive for this to work, but then:
> a) all concerns with alias are addressed
> b) D as a language becomes more symmetrical in the DBC realm
> c) extending design by contract is cool :)
> d) we are better than Java AND C++ because we can remove contracts
> e) constructors, etc might also be implicitly passed down and deleted where necessary.
>
>
> Whaddayathink?

I had a thought along those lines, however, it does not stop the problem where if you have:

class X1 {
}

..class X2 thru X8..

class X9 : X8 {
  void foo(int a) {
  }
}

void main()
{
  X9 x = new X9();
  short s;
  x.foo(s);
}

using the 'more inclusive' rules the above calls X9:foo _unless_ someone goes and adds a method to one of the preceeding 8 classes that is called 'foo' and takes a 'short' (or a short and some default parameters).

Also, in order to exclude something you need to know it exists, which means you have to trawl thru the entire class heirarcy and decide what to exclude, then if they add something new, you need to know about it, so you can decide whether to exclude that too.

I don't think this is manageable.

Sadly, I think 'alias' which is the opposite of what you have proposed, deliberate inclusion of things from base classes is better.

I say 'sadly' because it still seems counter-intuitive in general cases.


How about this for a counter proposal, we add an 'extends' keyword, which is used thus:

class Parent {
  void foo(int a) {..}
}

class Child : Parent {
  extends void foo(float a) {..}
}

void main()
{
  Child c = new Child();
  float a;
  int b;
  c.foo(a);  //Child:foo is called
  c.foo(b);  //Parent:foo is called
}

so the default behaviour remains the same, that is, methods of the same name 'overload' methods in the parent, but, by adding extends to the method in the child, we do an automatic 'alias' grabbing the parents methods of the same name.

How is that to everyones liking?

Regan.

-- 
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
July 29, 2004
On Thu, 29 Jul 2004 16:29:18 +1200, Regan Heath <regan@netwin.co.nz> wrote:
<snip>
> How about this for a counter proposal, we add an 'extends' keyword, which is used thus:
>
> class Parent {
>    void foo(int a) {..}
> }
>
> class Child : Parent {
>    extends void foo(float a) {..}
> }
>
> void main()
> {
>    Child c = new Child();
>    float a;
>    int b;
>    c.foo(a);  //Child:foo is called
>    c.foo(b);  //Parent:foo is called
> }
>
> so the default behaviour remains the same, that is, methods of the same name 'overload' methods in the parent, but, by adding extends to the method in the child, we do an automatic 'alias' grabbing the parents methods of the same name.
>
> How is that to everyones liking?
<snip>

It should be mentioned that this proposal does not solve the problem I mentioned, but rather you can only have that problem if you use 'extends' so should be sure when you use it.

In addition the problem I mentioned is perhaps caused by implicit casting of function parameters, what if we removed that, so all parameters must be explicitly cast to the desired type, then perhaps you could safely alias in all the parent methods all the time.

Regan.

-- 
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
July 29, 2004
On Thu, 29 Jul 2004 16:36:13 +1200, Regan Heath <regan@netwin.co.nz> wrote:

> On Thu, 29 Jul 2004 16:29:18 +1200, Regan Heath <regan@netwin.co.nz> wrote:
> <snip>
>> How about this for a counter proposal, we add an 'extends' keyword, which is used thus:
>>
>> class Parent {
>>    void foo(int a) {..}
>> }
>>
>> class Child : Parent {
>>    extends void foo(float a) {..}
>> }
>>
>> void main()
>> {
>>    Child c = new Child();
>>    float a;
>>    int b;
>>    c.foo(a);  //Child:foo is called
>>    c.foo(b);  //Parent:foo is called
>> }
>>
>> so the default behaviour remains the same, that is, methods of the same name 'overload' methods in the parent, but, by adding extends to the method in the child, we do an automatic 'alias' grabbing the parents methods of the same name.
>>
>> How is that to everyones liking?
> <snip>
>
> It should be mentioned that this proposal does not solve the problem I mentioned, but rather you can only have that problem if you use 'extends' so should be sure when you use it.
>
> In addition the problem I mentioned is perhaps caused by implicit casting of function parameters, what if we removed that, so all parameters must be explicitly cast to the desired type, then perhaps you could safely alias in all the parent methods all the time.

Looking back on Walters example..

class B
{    long x;
     void set(long i) { x = i; }
    void set(int i) { x = i; }
    long squareIt() { return x * x; }
}
class D : B
{
    long square;
    void set(long i) { B.set(i); square = x * x; }
    long squareIt() { return square; }
}

long foo(B b)
{
    b.set(3);
    return b.squareIt();
}

the above would still compile and produce the obscure bug even if parameters had to be explicitly cast.

However, a close look at the example, leads me to ask what the point of the:
  void set(int i) { x = i; }

function is? int is implicitly castable to long (as shown by the function itself) so the function isn't required. Removing it certainly fixes the problem, in addition I'd make the following change:
    void set(long i) { B.set(i); square = x * x; }
to:
    void set(long i) { B.set(i); square = B.squareIt(); }

IMO this example could be a red herring, engineered to show the bug, not at all likely in reality.

Regan.

-- 
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
July 29, 2004
Regan Heath wrote:

> On Wed, 28 Jul 2004 21:04:58 -0700, stonecobra <scott@stonecobra.com> wrote:
>>
>> Whaddayathink?
> 
> 
> I had a thought along those lines, however, it does not stop the problem where if you have:
> 
> class X1 {
> }
> 
> ...class X2 thru X8..
> 
> class X9 : X8 {
>   void foo(int a) {
>   }
> }
> 
> void main()
> {
>   X9 x = new X9();
>   short s;
>   x.foo(s);
> }
> 
> using the 'more inclusive' rules the above calls X9:foo _unless_ someone goes and adds a method to one of the preceeding 8 classes that is called 'foo' and takes a 'short' (or a short and some default parameters).

Yes, this would be the case either way.  This does not change between what we have now and this proposal.  If someone were to add a foo(short s) in X6, for example, the only difference at that point is that my proposal picks it up, and D currently does not.

> 
> Also, in order to exclude something you need to know it exists, which means you have to trawl thru the entire class heirarcy and decide what to exclude, then if they add something new, you need to know about it, so you can decide whether to exclude that too.

I would assume either way that you should know something exists ;)  I don't think that this changes anything there.  If you are too lazy to know it exists now, you will be too lazy to exclude it in this proposal.

> 
> I don't think this is manageable.

We agree to disagree.  You only exclude things when you want to limit the inheritance.  D does this now, but only when not using alias in a method overload situation, which does not seem terribly intuitive/symmetrical.

> 
> Sadly, I think 'alias' which is the opposite of what you have proposed, deliberate inclusion of things from base classes is better.

Because it can do less?  Either solution requires you know about what you want to do.  My solution allows you to do more.

<Garbage deleted>

Scott Sanders
July 29, 2004
On Thu, 29 Jul 2004 16:36:13 +1200, Regan Heath wrote:

> On Thu, 29 Jul 2004 16:29:18 +1200, Regan Heath <regan@netwin.co.nz> wrote: <snip>
>> How about this for a counter proposal, we add an 'extends' keyword, which is used thus:
>>
>> class Parent {
>>    void foo(int a) {..}
>> }
>>
>> class Child : Parent {
>>    extends void foo(float a) {..}
>> }
>>
>> void main()
>> {
>>    Child c = new Child();
>>    float a;
>>    int b;
>>    c.foo(a);  //Child:foo is called
>>    c.foo(b);  //Parent:foo is called
>> }
>>
>> so the default behaviour remains the same, that is, methods of the same name 'overload' methods in the parent, but, by adding extends to the method in the child, we do an automatic 'alias' grabbing the parents methods of the same name.
>>
>> How is that to everyones liking?
> <snip>
> 
> It should be mentioned that this proposal does not solve the problem I mentioned, but rather you can only have that problem if you use 'extends' so should be sure when you use it.
> 
> In addition the problem I mentioned is perhaps caused by implicit casting of function parameters, what if we removed that, so all parameters must be explicitly cast to the desired type, then perhaps you could safely alias in all the parent methods all the time.
> 
> Regan.

Now what would be useful (to me anyhow) would be the option of an annotated source code listing produced by the compiler that detailed things like which method was actually used, what implicit casting and promotions were actually used, which fragments of code were optimized out, etc...

The problem being that visual inspection of the code may not alert the reader to the decisions that the compiler might actually make. Maybe v3.0 ;-)

-- 
Derek
Melbourne, Australia
29/Jul/04 2:58:33 PM
July 29, 2004

stonecobra wrote:
> <Garbage deleted>

Sorry, my newsreader (Thunderbird) happily merged another messages headers in Regan's post.  I cut out the 'garbage'.  I did not mean to say that the rest Regan's repsonse was goarbage.  Apologies.

Scott Sanders
July 29, 2004
On Wed, 28 Jul 2004 22:08:24 -0700, stonecobra <scott@stonecobra.com> wrote:
> Regan Heath wrote:
>
>> On Wed, 28 Jul 2004 21:04:58 -0700, stonecobra <scott@stonecobra.com> wrote:
>>>
>>> Whaddayathink?
>>
>>
>> I had a thought along those lines, however, it does not stop the problem where if you have:
>>
>> class X1 {
>> }
>>
>> ...class X2 thru X8..
>>
>> class X9 : X8 {
>>   void foo(int a) {
>>   }
>> }
>>
>> void main()
>> {
>>   X9 x = new X9();
>>   short s;
>>   x.foo(s);
>> }
>>
>> using the 'more inclusive' rules the above calls X9:foo _unless_ someone goes and adds a method to one of the preceeding 8 classes that is called 'foo' and takes a 'short' (or a short and some default parameters).
>
> Yes, this would be the case either way.  This does not change between what we have now and this proposal.

Not true, currently the new method would be ignored, _unless_ you aliased it in.

> If someone were to add a foo(short s) in X6, for example, the only difference at that point is that my proposal picks it up, and D currently does not.

How does yours pick it up? I must have missed something..

>> Also, in order to exclude something you need to know it exists, which means you have to trawl thru the entire class heirarcy and decide what to exclude, then if they add something new, you need to know about it, so you can decide whether to exclude that too.
>
> I would assume either way that you should know something exists ;)

Not if it's buried at the bottom of a big class heirarcy, not if it's added after you derive your class.

> I don't think that this changes anything there.  If you are too lazy to know it exists now, you will be too lazy to exclude it in this proposal.

It's not a matter of laziness, why should you have to know all the public members of all the parent classes involved in a class you want to derive from.

>> I don't think this is manageable.
>
> We agree to disagree.  You only exclude things when you want to limit the inheritance.  D does this now, but only when not using alias in a method overload situation, which does not seem terribly intuitive/symmetrical.

I agree alias is not intuitive.

I say again, in order to exclude something you have to know it exists, how does your proposal help if they add a method _after_ you have derived your class?

>> Sadly, I think 'alias' which is the opposite of what you have proposed, deliberate inclusion of things from base classes is better.
>
> Because it can do less?

Because it does not allow nasty silent bugs by default as yours does.

> Either solution requires you know about what you want to do.  My solution allows you to do more.

I'm not conviced it does.

Regan.

-- 
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
July 29, 2004
Regan Heath wrote:
> On Wed, 28 Jul 2004 22:08:24 -0700, stonecobra <scott@stonecobra.com> wrote:
> 
>> Regan Heath wrote:
>>
>>> On Wed, 28 Jul 2004 21:04:58 -0700, stonecobra <scott@stonecobra.com> wrote:
>>>
>>>>
>>>> Whaddayathink?
>>>
>>>
>>>
>>> I had a thought along those lines, however, it does not stop the problem where if you have:
>>>
>>> class X1 {
>>> }
>>>
>>> ...class X2 thru X8..
>>>
>>> class X9 : X8 {
>>>   void foo(int a) {
>>>   }
>>> }
>>>
>>> void main()
>>> {
>>>   X9 x = new X9();
>>>   short s;
>>>   x.foo(s);
>>> }
>>>
>>> using the 'more inclusive' rules the above calls X9:foo _unless_ someone goes and adds a method to one of the preceeding 8 classes that is called 'foo' and takes a 'short' (or a short and some default parameters).
>>
>>
>> Yes, this would be the case either way.  This does not change between what we have now and this proposal.
> 
> 
> Not true, currently the new method would be ignored, _unless_ you aliased it in.

Sorry, you are correct.  I misread what you said.

> 
>> If someone were to add a foo(short s) in X6, for example, the only difference at that point is that my proposal picks it up, and D currently does not.
> 
> 
> How does yours pick it up? I must have missed something..

If you mean 'pick it up' aka use it, my proposal's 'along with scoping rules that were more inclusive', ie always find a signature match, it would be picked up.

> 
>>> Also, in order to exclude something you need to know it exists, which means you have to trawl thru the entire class heirarcy and decide what to exclude, then if they add something new, you need to know about it, so you can decide whether to exclude that too.
>>
>>
>> I would assume either way that you should know something exists ;)
> 
> 
> Not if it's buried at the bottom of a big class heirarcy, not if it's added after you derive your class.

I would argue the opposite.  You are responsible for knowing about it. If you did not overload the method, it still exists in your classes contract today, so you had better know about it.

> 
>> I don't think that this changes anything there.  If you are too lazy to know it exists now, you will be too lazy to exclude it in this proposal.
> 
> 
> It's not a matter of laziness, why should you have to know all the public members of all the parent classes involved in a class you want to derive from.

Why else would you choose to derive from this class in particular.  OO programming does not allow you to shirk from your programming duties here.  It is just less forgiving if you do.

> 
>>> I don't think this is manageable.
>>
>>
>> We agree to disagree.  You only exclude things when you want to limit the inheritance.  D does this now, but only when not using alias in a method overload situation, which does not seem terribly intuitive/symmetrical.
> 
> 
> I agree alias is not intuitive.
> 
> I say again, in order to exclude something you have to know it exists, how does your proposal help if they add a method _after_ you have derived your class?

Again, you are responsible for knowing if you choose to subclass, IMHO.

> 
>>> Sadly, I think 'alias' which is the opposite of what you have proposed, deliberate inclusion of things from base classes is better.
>>
>>
>> Because it can do less?
> 
> 
> Because it does not allow nasty silent bugs by default as yours does.

It is causing them today.  Suppose you have aliased, and one method is added in the middle after the fact as you suggest, the same behavior results.  Another problem with alias is that it is not explicit by method parameters, so it causes a whole other class of obscure bugs.

Your comment on explicit typing would definetly help here, on either side.

I say it again, being lazy and not knowing the contract (not the implementation, just the method signatures) you are passing out as a programmer will lead to problems down the road...

> 
>> Either solution requires you know about what you want to do.  My solution allows you to do more.
> 
> 
> I'm not conviced it does.

One other thing you did not address with the current setup is how we take something away, which we cannot do today in D or any other language that I know.

Scott
July 29, 2004
On Wed, 28 Jul 2004 22:39:16 -0700, stonecobra <scott@stonecobra.com> wrote:
>>> If someone were to add a foo(short s) in X6, for example, the only difference at that point is that my proposal picks it up, and D currently does not.
>>
>>
>> How does yours pick it up? I must have missed something..
>
> If you mean 'pick it up' aka use it, my proposal's 'along with scoping rules that were more inclusive', ie always find a signature match, it would be picked up.

You said 'pick it up' I wanted to know what you meant :) So you mean't it includes it in the current scope, not that it notices the bug.

>>>> Also, in order to exclude something you need to know it exists, which means you have to trawl thru the entire class heirarcy and decide what to exclude, then if they add something new, you need to know about it, so you can decide whether to exclude that too.
>>>
>>>
>>> I would assume either way that you should know something exists ;)
>>
>>
>> Not if it's buried at the bottom of a big class heirarcy, not if it's added after you derive your class.
>
> I would argue the opposite.  You are responsible for knowing about it. If you did not overload the method, it still exists in your classes contract today, so you had better know about it.

To quote Walter quoting Stroustrup..

"Stroustrup rejects the
idea of ignoring scope issues when doing overload resolution. He argues that
it would be surprising in a deeply nested heirarchy of class derivation,
that a user of a derived class would have to know too much detail about a
base class that may be hidden. Also, he shows how surprising errors can
creep in, such as if a function in base class B copies only the B part of an
object, but was inadvertantly called with an instance of D that didn't
override each of those B functions properly."

I currently agree with this.

>>> I don't think that this changes anything there.  If you are too lazy to know it exists now, you will be too lazy to exclude it in this proposal.
>>
>>
>> It's not a matter of laziness, why should you have to know all the public members of all the parent classes involved in a class you want to derive from.
>
> Why else would you choose to derive from this class in particular.

Generally to _add_ functionality.

> OO programming does not allow you to shirk from your programming duties here.  It is just less forgiving if you do.

Why is it shirking, you're adding to an existing class, in doing so you do not need to know all of the things it does. All you really need to know is that it doesn't do the thing you're adding.

>>>> I don't think this is manageable.
>>>
>>>
>>> We agree to disagree.  You only exclude things when you want to limit the inheritance.  D does this now, but only when not using alias in a method overload situation, which does not seem terribly intuitive/symmetrical.
>>
>>
>> I agree alias is not intuitive.
>>
>> I say again, in order to exclude something you have to know it exists, how does your proposal help if they add a method _after_ you have derived your class?
>
> Again, you are responsible for knowing if you choose to subclass, IMHO.

Why? Whatever it does will still be present, unless you are overriding it (see my arguments elsewhere for a mandatory override keyword in this case) so it doesn't matter what it is.

IMO you only need to know the entire class heirarcy _if_ you change the behaviour to what you propose.

>>>> Sadly, I think 'alias' which is the opposite of what you have proposed, deliberate inclusion of things from base classes is better.
>>>
>>>
>>> Because it can do less?
>>
>>
>> Because it does not allow nasty silent bugs by default as yours does.
>
> It is causing them today.  Suppose you have aliased, and one method is added in the middle after the fact as you suggest, the same behavior results.

Yep, but the difference is, you have chosen to alias. So the bug will only occur if you choose to take the risk.

> Another problem with alias is that it is not explicit by method parameters, so it causes a whole other class of obscure bugs.

If it were explicit you'd have to alias every single method, kinda the opposite to what you're suggesting, I don't think you'll find very many people in favour of this, it's basically forcing you to re-write the entire base class public interface.

> Your comment on explicit typing would definetly help here, on either side.

Perhaps.. I'm not convinced my idea is the right solution. It just occured to me that an implicit cast was causing the incorrect behaviour, and that implicit casts happen silently.

> I say it again, being lazy and not knowing the contract (not the implementation, just the method signatures) you are passing out as a programmer will lead to problems down the road...

Perhaps we should agree to disagree.

>>> Either solution requires you know about what you want to do.  My solution allows you to do more.
>>
>>
>> I'm not conviced it does.
>
> One other thing you did not address with the current setup is how we take something away, which we cannot do today in D or any other language that I know.

That is because this goes against the OO idea that when subclassing you're _adding_ functionality, not taking it away.

Regan

-- 
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/