July 26, 2004
"Lars Ivar Igesund" <larsivar@igesund.net> wrote in message news:ce1bpg$2rjr$2@digitaldaemon.com...
> Walter wrote:
> > The rule to apply is straightforward - look up the name, *then* apply overload resolution.
>
> The rule would still be straightforward if it was:
>
> look up the name, apply overload resolution
> if overload resolution fails; repeat above for superclass

That's a good thought, but overload resolution is not pass/fail, it has multiple levels of matching. You'll have to decide if a worse match in the current scope is better or worse than a better match in another scope. It'll be an arbitrary rule, one more bit of arcana needed to program in D.


July 26, 2004
Lars made a series of valid points in his three emails Walter; as have many others. The fact that you choose to divert focus by ignoring his core points does not serve you well. You have been asked to clarify the /benefits/ of the current implementation several times now, in various ways, and still you avoid the issue at hand. One might be given pause to think you're avoiding this at all costs ...

Please; I ask you once again: show us /all/ why this C++ style method-hiding is necessary and/or beneficial to D programmers via one or two examples. As I've already stated, if the benefits are so obvious then it won't take much effort on your part to do so.

Posting that example of how C++ operates had absolutely zero value: we already /know/ it works that way ~ that's exactly the point of this issue ~ there's no perceived need for it to be that way in D at all!

If you cannot show any benefits (for D) with some solid examples, then your position on this becomes seriously invalidated. Period. The whole point of this is to improve the D language; not to posture and swagger over academic excrement  ...

- Kris


"Walter" <newshound@digitalmars.com> wrote in message news:ce24ek$b1e$1@digitaldaemon.com...
>
> "Lars Ivar Igesund" <larsivar@igesund.net> wrote in message news:ce1ccn$2ru8$1@digitaldaemon.com...
> > Argumentation for why it should work the way it do today is lacking, and the implementation/specification is unintuitive, badly documented and works against most common use.
> >
> > All IMHO.
>
> I can concur with "badly documented" <g> but why is the way C++ has worked for 20 years against most common use?
>
>


July 26, 2004
What you specifically avoid in your reply, Walter, is that an EXACT (yes, that is a shout) match may be in the very next scope. Additionally, the rules involved are no different that those applied for the current scope; there's simply more signatures to choose from. You really appear to be deliberately obfuscating the issue ... why?

- Kris


"Walter" <newshound@digitalmars.com> wrote in message news:ce2676$c4d$1@digitaldaemon.com...
>
> "Lars Ivar Igesund" <larsivar@igesund.net> wrote in message news:ce1bpg$2rjr$2@digitaldaemon.com...
> > Walter wrote:
> > > The rule to apply is straightforward - look up the name, *then* apply overload resolution.
> >
> > The rule would still be straightforward if it was:
> >
> > look up the name, apply overload resolution
> > if overload resolution fails; repeat above for superclass
>
> That's a good thought, but overload resolution is not pass/fail, it has multiple levels of matching. You'll have to decide if a worse match in the current scope is better or worse than a better match in another scope.
It'll
> be an arbitrary rule, one more bit of arcana needed to program in D.
>
>


July 26, 2004
"Kris" <someidiot@earthlink.dot.dot.dot.net> wrote in message news:cdvjvc$1osa$1@digitaldaemon.com...
> "Walter" <newshound@digitalmars.com> wrote in message news:cdvhgv$1nf1$1@digitaldaemon.com...
> > Stroustrup has laid them out in chapter 13.1 of the "The Annotated C++ Reference Manual." The general rule for overloading is that a name is
> looked
> > up first, and then overload resolution is applied. 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.
> >
> >
>
> That is indeed the C++ view. However you forget a number of things:
>
> 1) D is better in so many ways than C++. It says so right there in the comparison charts ... <g>

Stroustrup's arguments apply equally to D.

> 2) C++ had to deal with multiple inheritance ~ I imagine that's a big
factor
> in the above statement. D (and Java) do not.

This behavior of C++ predated MI by several years, and his argument is not about MI.

> Notice how Java dispensed with the 'alias' notion completely, and did not
pay this 'Stroustrup Tax' in any
> shape or form.

I am not an experienced Java programmer, so I cannot give you an in-depth view of this from real world Java experience. But I do know that Java has made some serious errors in its language design, such as exception specifications (even though many Java programmers still believe it is a feature, not a bug <g>). So, I don't necessarilly believe that Java necessarilly got things right because Java is successful. On the other hand, I have a lot of real world experience with C++ and where programmers routinely crash & burn with it, and this behavior of C++ just doesn't come up as needing fixing.

> 3) Aliasing method names is truly non-intuitive. It stands out like a sore thumb within D as a kludge. It does not matter from whence it came, or
what
> supposed pedigree it has ~ so many other things in D are completely
natural.

That all depends on one's perspective. Aliasing symbols in D is the usual way to bring a name from one scope into another. It would seem counter-intuitive for this to not work with method names <g>.

> 4) You may find that a required use of  "override" would alleviate some of those Stroustrup concerns.

I think that would require the base class designer to know about the derived classes, something that goes against OOP principles.

> 5) You are at least as smart as B.S., and can do much better than this. I can't help but be entirely suspicious about the B.S. Gospel applying in
the
> same manner.

Bjarne Stroustrup is a very smart man, and I've found his arguments are well founded in experience and rationality and not gospel. That doesn't mean I always agree with his conclusions, but one needs to have all one's ducks in a row if one wants to challenge his conclusions.

> I challenge you, Walter, to exceed C++ in this respect also.

It's entirely possible that two reasonable people can look at the same data and draw different conclusions. In this case, which is better is a matter of opinion, and reasonable people can disagree. Using an alias declaration neatly produces the behavior you desire, but as another pointed out, if the Java rules were the default there doesn't seem to be nearly so simple a way to go the other way.

> I further challenge you to provide a reasonable D example showing where
this
> kind of alias thing is actually beneficial to the D programmer. If you cannot do that, then this Stroustrup argument holds no water whatsoever. Since we're all required to spend a bunch of time whittling down code in
bug
> reports for you, I think it's only fair you do the same in return with respect to issues like this. I mean, if the value of alias is so obvious, how much effort would a few good examples really take?

Stroustrup gives two examples (slightly modified here):

---------------------------------
class X1 { void f(int); }

 // chain of derivations X(n) : X(n-1)

class X9: X8 { void f(double); }

void g(X9 p)
{
    p.f(1);    // X1.f or X9.f ?
}
-----------------------------------
His argument is that one can easilly miss an overload of f() somewhere in a complex class heirarchy, and argues that one should not need to understand everything about a class heirarchy in order to derive from it. The other example involves operator=(), but since D doesn't allow overloading operator=() instead I'll rewrite it as if it were a function that needs to alter a class state, and a derived class written later that 'caches' a computation on the derived state:

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

Now, imagine B were a complex class with a lot of stuff in it, and our optimizing programmer missed the existence of set(int). Then, one has:

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

and we have an obscure bug.


July 26, 2004
"Walter"  wrote ..
> C'mon, Kris! You import the names into a scope, and then want the names to not be found. I don't think that is a resolvable problem. I admit to the documentation being inadequate, but after I explained the 4 simple steps
of
> name lookup rules and when in the process overload resolution applies, the behavior you've seen is straightforward. C++ has loads of magic special rules that apply in arcane situations, and nobody understands them, they just dink around with the source until it appears to work.
>
> I'll be happy to work on any forward referencing issues.


Once again, Walter; you appear to be deliberately ignoring the core issue. I most certainly did not state anything of the sort, regarding your opening salvo. I absolutely do want the names to be found: all signatures! That's the problem here!

And again, this is not C++.  How many people have to say that to you? It's not good enough to just DINK AROUND until it works, as you say. Unless this is just a bit of a laugh for you? If it is, then I'll stop wasting my time with your little game and move on.

Try to look at this from an objective standpoint please?

1) Some external names were imported. Period.
2) They happened to conflict with some existing class method names. Period.
3) Instead of overloading, the imports hid the internal names completely
from the outside world. No errors. Period.
4) In this case, when switching from Win32 to Linux, a bunch of class
methods mysteriously disappeared from view. Period.
5) Hours of debugging time was spent tracking down thoroughly misleading
compiler error messages. Period.

This maketh a problem Walter. Not a personal opinion. Yes, you explained the four steps and they simply point out the serious limitations of the C++ model as applied to D. Indeed, you can simply cover up said limitations by not allowing imports within a class scope. Fair enough.

I simply pointed out why I was taking advantage of the inner import, with respect to "locality of reference" vis-a-vis maintainability. For doing so, I proffer my deepest apologies.

- Kris


July 26, 2004
Walter wrote:

> Stroustrup gives two examples (slightly modified here):
> 
> ---------------------------------
> class X1 { void f(int); }
> 
>  // chain of derivations X(n) : X(n-1)
> 
> class X9: X8 { void f(double); }
> 
> void g(X9 p)
> {
>     p.f(1);    // X1.f or X9.f ?
> }
> -----------------------------------

This creates a conflict and an explicit cast should be required.

> His argument is that one can easilly miss an overload of f() somewhere in a
> complex class heirarchy, and argues that one should not need to understand
> everything about a class heirarchy in order to derive from it. The other
> example involves operator=(), but since D doesn't allow overloading
> operator=() instead I'll rewrite it as if it were a function that needs to
> alter a class state, and a derived class written later that 'caches' a
> computation on the derived state:
> 
> 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; }
> }
> 
> Now, imagine B were a complex class with a lot of stuff in it, and our
> optimizing programmer missed the existence of set(int). Then, one has:
> 
>     long foo(B b)
>     {
>         b.set(3);
>         return b.squareIt();
>     }
> 
> and we have an obscure bug.

This is clearly bad design/programming of the class D. If you override some methods of B deliberately (as this person clearly do, he both use B.x and B.set), you damn sure need to know what's in B. Whether programmers doing this is able to make a good OO program at all, I highly doubt. (I don't imply that this example show your skill level in OOP, Walter :), but you should find a better example.)

Lars Ivar Igesund
July 26, 2004
Thank you for the examples.

"Walter"  wrote .
> Stroustrup's arguments apply equally to D.

But yet again you fail to qualify why

> I am not an experienced Java programmer, so I cannot give you an in-depth view of this from real world Java experience. But I do know that Java has made some serious errors in its language design, such as exception specifications (even though many Java programmers still believe it is a feature, not a bug <g>). So, I don't necessarilly believe that Java necessarilly got things right because Java is successful. On the other
hand,
> I have a lot of real world experience with C++ and where programmers routinely crash & burn with it, and this behavior of C++ just doesn't come up as needing fixing.

Nobody is saying that Java got everything right Walter. If it did, we wouldn't be here now, would we? I happen to agree with you that the Exception problem is very real, but let's not divert our focus yet again please. The point remains: if Java can handle it, then so can D. Rather than give that assertion some credence, you appear to brush it off as not even worth looking into.

> > 4) You may find that a required use of  "override" would alleviate some
of
> > those Stroustrup concerns.
>
> I think that would require the base class designer to know about the
derived
> classes, something that goes against OOP principles.

The D "override" keyword does not belong in a base class, as you well know. Instead, it goes into a derived class. I was referring to the debate over whether "override" should be mandatory or not, and perhaps that might help out with some of these concerns. Apparently it does not.

> opinion, and reasonable people can disagree. Using an alias declaration neatly produces the behavior you desire, but as another pointed out, if
the
> Java rules were the default there doesn't seem to be nearly so simple a
way
> to go the other way.

That's perhaps because you haven't given it very much thought? Do you perhaps think that not even one person on the NG could suggest a better approach? Or is that simply not an acceptable way to enhance the language?

- Kris


July 26, 2004
On Sun, 25 Jul 2004 23:18:41 -0700, Walter wrote:
> "Kris" <someidiot@earthlink.dot.dot.dot.net> wrote in message news:cdvjvc$1osa$1@digitaldaemon.com...

[snip]

>> I further challenge you to provide a reasonable D example showing where
> this
>> kind of alias thing is actually beneficial to the D programmer. If you cannot do that, then this Stroustrup argument holds no water whatsoever. Since we're all required to spend a bunch of time whittling down code in
> bug
>> reports for you, I think it's only fair you do the same in return with respect to issues like this. I mean, if the value of alias is so obvious, how much effort would a few good examples really take?
> 
> Stroustrup gives two examples (slightly modified here):
> 
> ---------------------------------
> class X1 { void f(int); }
> 
>  // chain of derivations X(n) : X(n-1)
> 
> class X9: X8 { void f(double); }
> 
> void g(X9 p)
> {
>     p.f(1);    // X1.f or X9.f ?
> }
> -----------------------------------
> His argument is that one can easilly miss an overload of f() somewhere in a complex class heirarchy, and argues that one should not need to understand everything about a class heirarchy in order to derive from it.

This example has now convinced me the both Stroustrup and Walter have got it wrong. At first, I thought that all I need do is make some explicit casts, but...

Here is some D code to show it's madness...

<code>
class X1     { void f(int x)   {printf("1\n");} }
class X2: X1 { void f(long x)  {printf("2\n");} }
class X3: X2 { void f(uint x)  {printf("3\n");} }
class X4: X3 { void f(char x)  {printf("4\n");} }
class X5: X4 { void f(double x){printf("5\n");} }
void main()
{
  X5 p = new X5;
  p.f(cast(int)1);
  p.f(cast(long)1);
  p.f(cast(uint)1);
  p.f(cast(char)1);
  p.f(cast(double)1);
  p.f(1);
}
</code>

And the result...

 c:\temp>dmd test
 C:\DPARNELL\DMD\BIN\..\..\dm\bin\link.exe test,,,user32+kernel32/noi;

 c:\temp>test
 5
 5
 5
 5
 5
 5

 c:\temp>


WHAT!?!? The coder has *explicitly* said he wanted an 'int'/'long'/'uint'... argument but D just went and converted to 'double' anyhow. Even the non-cast call did *not* use a double as the argument value; 1 is an integer not any form of floating point number.

Then I changed X5 to include the 'alias X1.f f;' line and get this message...

  test.d(10): function f overloads void(double x) and void(int x) both
  match argument list for f

WHAT?!?!? Since when is (double x) and (int x) the same? This is *not* a
match.

This current method matching rule is starting to seem more like lunacy than sanity. I thought compilers were supposed to help coders. This is just making more work rather than less work. Here is the code to make it work the 'intuitive' way...

<code>
class X1     { void f(int x)   {printf("1\n");} }
class X2: X1 { void f(long x)  {printf("2\n");}
               alias X1.f f;
             }
class X3: X2 { void f(uint x)  {printf("3\n");}
               alias X1.f f;
               alias X2.f f;
             }
class X4: X3 { void f(char x)  {printf("4\n");}
               alias X1.f f;
               alias X2.f f;
               alias X3.f f;
             }
class X5: X4 { void f(double x){printf("5\n");}
               alias X1.f f;
               alias X2.f f;
               alias X3.f f;
               alias X4.f f;
             }
void main()
{
  X5 p = new X5;

  p.f(cast(int)1);
  p.f(cast(long)1);
  p.f(cast(uint)1);
  p.f(cast(char)1);
  p.f(cast(double)1);
  p.f(1);
}
</code>

<output>
c:\temp>dmd test
C:\DPARNELL\DMD\BIN\..\..\dm\bin\link.exe test,,,user32+kernel32/noi;

c:\temp>test
1
2
3
4
5
1
</output>
And that's just for *one* method.

> The other
> example involves operator=(), but since D doesn't allow overloading
> operator=() instead I'll rewrite it as if it were a function that needs to
> alter a class state, and a derived class written later that 'caches' a
> computation on the derived state:
> 
> 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; }
> }
> 
> Now, imagine B were a complex class with a lot of stuff in it, and our optimizing programmer missed the existence of set(int). Then, one has:
> 
>     long foo(B b)
>     {
>         b.set(3);
>         return b.squareIt();
>     }
> 
> and we have an obscure bug.

This type of bug is analogous to a spelling mistake. Its similar to the situation where two methods are spelled almost identically and the coder either chooses the wrong one or mistypes it. It is not a fault of OO or the language, but of the coder or class designer.


I really don't mind being convinced that Walter (D) has got it right, but this argument doesn't do it.

-- 
Derek
Melbourne, Australia
26/Jul/04 4:45:21 PM
July 26, 2004
In article <ce27vn$diq$1@digitaldaemon.com>, Walter says...

>Stroustrup gives two examples (slightly modified here):
>
>---------------------------------
>class X1 { void f(int); }
>
> // chain of derivations X(n) : X(n-1)
>
>class X9: X8 { void f(double); }
>
>void g(X9 p)
>{
>    p.f(1);    // X1.f or X9.f ?
>}
>-----------------------------------

X1.f, obviously. It's an exact match. There is no ambiguity there.


>His argument is that one can easilly miss an overload of f() somewhere in a
>complex class heirarchy,

Then one should RTFM. Java, of course, has a compiler which can automatically generate documentation, which is a big boon. When you look at the docs for a Java class, you see an inheritance diagram, and can click on the superclass to see /its/ documentation.

I believe Doxygen can do something like that for us in D, but not everyone uses it, and I'm not completely sure how it can connect a class to its superclass if they were written by different authors and exist in two completely independent modules. For example, everything derives from Object, but Object is not doxygenated. Java's Object class, by contrast, is fully Javadocked.

It seems to me that this is actually an argument in favor of properly documenting everything in an integrated way.

Another way to look at this is, this is perhaps a place where a lint-like-tool (OT: Why is it called "lint" anyway?) might issue a warning. But since D itself doesn't do warnings, it should just go ahead and compile it.


>and argues that one should not need to understand
>everything about a class heirarchy in order to derive from it.

But why not?

Okay, second 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; }
>}
>
>Now, imagine B were a complex class with a lot of stuff in it, and our optimizing programmer missed the existence of set(int). Then, one has:
>
>    long foo(B b)
>    {
>        b.set(3);
>        return b.squareIt();
>    }
>
>and we have an obscure bug.

<sarcasm>I take it you haven't heard of something called "design by contract" then? It's a feature of D which C++ does not possess.</sarcasm>


#    class D : B
#    {
#        invariant
#        {
#            assert(square == i * i);
#        }
#    }



July 26, 2004
In article <ce27vn$diq$1@digitaldaemon.com>, Walter says...
>
>
[...]
>His argument is that one can easilly miss an overload of f() somewhere in a complex class heirarchy, and argues that one should not need to understand everything about a class heirarchy in order to derive from it. The other example involves operator=(), but since D doesn't allow overloading operator=() instead I'll rewrite it as if it were a function that needs to alter a class state, and a derived class written later that 'caches' a computation on the derived state:

I translated your example in Java, and it shows the "wrong" (non-C++) behaviour. The attached Test.java prints:

The square is: 0

Ciao