www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Extending DBC (was: The Alias peek-a-boo Game)

reply stonecobra <scott stonecobra.com> writes:
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
Jul 28 2004
parent reply Regan Heath <regan netwin.co.nz> writes:
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/
Jul 28 2004
next sibling parent reply Regan Heath <regan netwin.co.nz> writes:
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?

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/
Jul 28 2004
next sibling parent Regan Heath <regan netwin.co.nz> writes:
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?

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/
Jul 28 2004
prev sibling parent Derek Parnell <derek psych.ward> writes:
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?

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
Jul 28 2004
prev sibling parent reply stonecobra <scott stonecobra.com> writes:
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
Jul 28 2004
next sibling parent stonecobra <scott stonecobra.com> writes:
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
Jul 28 2004
prev sibling parent reply Regan Heath <regan netwin.co.nz> writes:
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/
Jul 28 2004
parent reply stonecobra <scott stonecobra.com> writes:
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
Jul 28 2004
parent Regan Heath <regan netwin.co.nz> writes:
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/
Jul 29 2004