July 26, 2004
Berin Loritsch wrote:
> 
> For now, I have to assume that you meant to write ReadsWrites in place of ReadingWriter in the extends/implements area of the last two classes.  If it does not compile in this case, I would be inclined to agree with you.
Good point. Yes I did mean:

class WritingReader : Reader, Writes {
    void writeSomething() { printf( "write2()" ); }
}

class ReadingWriter : Writer, Reads {
    void readSomething() { printf( "read2()" ); }
}

Even with the change the the compiler still crashes.

> 
> I do realize that D is not Java, but it does seem very cumbersome to me to see a class have all methods necessary for an interface through inheritance, but it doesn't compile or run correctly.
> 
> Question:  wouldn't it be equally valid to have this:
> 
> interface ReadsWrites : Reads, Writes {}
> 
> wich would in turn have the readSomething() and writeSomething() methods inherited from the super-interfaces?

This compiles:

interface Reads {
    void readSomething();
}

interface Writes {
    void writeSomething();
}

interface ReadsWrites : Reads, Writes {}

As for whether I would use that instead...
I think it depends on the sort of code you are writing.

When writing library or large app code the Reads and Writes interfaces could be in different files and/or directories. The way I did it saves having to hunt for the source files and or docs to get the return type and function parameters.

However code written for a smaller task would probably view interfaces as clutter and want to make them as terse as possible. Of course for that reason the code is unlikely to even use interfaces.
July 26, 2004
(Note this is a repost which corrects an example with a bug that crashes
the compiler. Note the corrected version also crashes the compiler and
thus I hope keeping the bug from be written off warrants reposting.)

Walter wrote:

> 
> Why is simply omitting the redundant interface name in the inheritance list
> a problem? I don't get it.
> 

Initially I was inclined to side with you here and suggest you simply make it an error to include a redundant interface name and finally put the matter to rest. However it is not that simple. Consider:
================================
interface Reads {
    void readSomething();
}

interface Writes {
    void writeSomething();
}

interface ReadsWrites {
    void readSomething();
    void writeSomething();
}

class Reader : Reads {
    void readSomething() { printf( "read1()" ); }
}

class Writer : Writer{
    void writeSomething() { printf( "write1()" ); }
}

class WritingReader : Reader, Writes {
    void writeSomething() { printf( "write2()" ); }
}

class ReadingWriter : Writer, Reads {
    void readSomething() { printf( "read2()" ); }
}
================================
There are no redundant interfaces in this example and yet ReadingWriter and WritingReader are not considered valid implementations.

BUG NOTE!!!
The above code crashes the compiler when I try to compile it so you may want to note this as a bug. (win32 version)
July 26, 2004
You may have a typo here:

> class Writer : Writer{

Should be "Writer : Writes" instead? That's probably what's choking the compiler. Other than that, then yes. You have to stub & dispatch for each of the inherited methods. It's no big deal with small interfaces, but quickly becomes messy (and brittle) once complexity ramps up to any significant degree.

BTW, the redundant Interface thing was a complete red-herring :-)  I made a mistake in my initial tardy example, and subsequently Walter thought that's what the issue was. My apologies.

- Kris


"parabolis" <parabolis@softhome.net> wrote in message news:ce459o$1jnu$1@digitaldaemon.com...
> (Note this is a repost which corrects an example with a bug that crashes the compiler. Note the corrected version also crashes the compiler and thus I hope keeping the bug from be written off warrants reposting.)
>
> Walter wrote:
>
> >
> > Why is simply omitting the redundant interface name in the inheritance
list
> > a problem? I don't get it.
> >
>
> Initially I was inclined to side with you here and suggest you simply
> make it an error to include a redundant interface name and finally put
> the matter to rest. However it is not that simple. Consider:
> ================================
> interface Reads {
>      void readSomething();
> }
>
> interface Writes {
>      void writeSomething();
> }
>
> interface ReadsWrites {
>      void readSomething();
>      void writeSomething();
> }
>
> class Reader : Reads {
>      void readSomething() { printf( "read1()" ); }
> }
>
> class Writer : Writer{
>      void writeSomething() { printf( "write1()" ); }
> }
>
> class WritingReader : Reader, Writes {
>      void writeSomething() { printf( "write2()" ); }
> }
>
> class ReadingWriter : Writer, Reads {
>      void readSomething() { printf( "read2()" ); }
> }
> ================================
> There are no redundant interfaces in this example and yet ReadingWriter
> and WritingReader are not considered valid implementations.
>
> BUG NOTE!!!
> The above code crashes the compiler when I try to compile it so you may
> want to note this as a bug. (win32 version)


July 27, 2004
On Mon, 26 Jul 2004 02:12:04 -0700, Walter <newshound@digitalmars.com> wrote:

>
> "Lars Ivar Igesund" <larsivar@igesund.net> wrote in message
> news:ce2a40$f4t$1@digitaldaemon.com...
>> 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.
>
> Of course - and that's the problem. It's an easy mistake to make, in my
> opinion, too easy.
>
>> 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.)
>
> It's obvious what's wrong when this is boiled down to such a tiny example.
> It isn't quite so obvious when it is married to all the usual cruft one
> finds in a class. Analogously, we both know that this C++ code:
>
>     int *p;
>     *p = 6;
>
> is bad code, and of course we wouldn't write that. But that kind of problem
> does crop up because it can be buried in a lot of other code. D has
> automatic initialization of variables to expose such problems. The same
> applies to the overloading/overriding issue. It's designed to make such
> unintentional mistakes unlikely - to overload based on methods from another
> scope, you have to do it intentionally (using the alias declaration).

Aren't you only 'overloading/overriding' if the function signature is identical?
Otherwise you're providing a specialisation i.e.

[oload1.d]
module oload1;
void abc(int abc) {}

[oload2.d]
module oload2;
import oload1;

void abc(long abc) {}

void main()
{
	foo(1);
	foo(1L);
}

The above prints "long" twice, which isn't desired.

The 'abc' provided by oload2 does _not_ override 'abc' provided by oload1 it's a different function signature.

I agree that if it did override, it should be an error, one you could catch at compile time, one that you could solve by using an alias to specify the one you mean.

Regan.

-- 
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
July 27, 2004
"parabolis" <parabolis@softhome.net> wrote in message news:ce43mo$1j7c$1@digitaldaemon.com...
> What is condemning is a violation without a reason. Your reason seems to be that 1) and 2) are not possible without a typehole.

Yes, that's exactly the reason I gave. It's not a violation without a reason.

> If you are right then there can be no similar
> language without the typehole. However Kris believes that
> Java is similar enough language to be a counter example.

Maybe Java does indeed have a type hole in it.

> Am I correct in assuming that dmd\src\phobos\internal\mars.h accurately portrays the memory layout? (ie a pointer to a Vtbl in memory points to a 32 bit value len which is followed by a 32 bit pointer to len 32 bit pointers)

The vtbls work just like in C++ single inheritance, except that the first entry is reserved for a reference to the .classinfo for the type.

> If so I am curious why an interface would need a Vtbl.

For two reasons: 1) efficiency - polymorphic function dispatch via vtbl[] has proven to be very efficient. 2) compatibility with COM vtbl[]s.

> Actually the fact that there is an Interface struct at
> all make me suspicious.

That's needed so that downcasting of interfaces can work.

> ================================
> (disclaimer - I have no idea how the following is actually
> done. Any pointers to more info would be appreciated.)
> ================================

The best way to figure it out is to write a couple bits of sample code, compile them, and obj2asm the result. Also, check out phobos/internal/cast.d.



July 27, 2004
Sean Kelly wrote:

> interface I { void foo( char x ); }
> class A { void foo( char x ) {} }
> class B : A {}
> class C : B {}
> class D : C {}
> class E : D {}
> class F : E, I { void foo( int x ) {} }
> 
> Assume that all the above classes have very long definitions and that A, B, and
> C are in a third-party library with no documentation and convoluted, badly
> formatted import files.  As the creator of F I may very well have intended to
> define F.foo as the implementation for I.foo and just screwed up the prototype.
> With the existing lookup scheme this is flagged as an error but with the
> proposed scheme it would compile just fine.  Further assume that A.foo is for
> some completely different purpose as A may have no knowledge of interface I.
> IMO the proposed scheme will be the cause of some very hard to find bugs.

I see your point and that would be a nasty bug to track down. In this
instance the inheritance bug actually seems to be a feature. But I believe that your argument misses a subtle point.

You argue that in the current D language, call it Dc, your error is caught. In the proposed language, call it Dp, you error is not caught and a bug is born. So you would like to believe that the total number of bugs in Dp is 1 more than Dc.

However you failed to account for the bugs in the Dc library that are not in the Dp library. I believe there would be more bugs for two reasons:

  1) If you can make a mistake like the one above, then consider
the library writers who were forced to sprinkle extra function stubs throughout their code.

  2) Since Dc has an exception to a rule that Dp does not you must ask yourself how many library writers only partly understand the exception and end up writing buggy code.




July 27, 2004
Kris wrote:

> You may have a typo here:
> 
> 
>>class Writer : Writer{
> 
> 
> Should be "Writer : Writes" instead? That's probably what's choking the

Thanks. I was wrong, there is no bug.

July 27, 2004
On Mon, 26 Jul 2004 19:12:00 +0000 (UTC), Sean Kelly <sean@f4.ca> wrote:
> In article <ce3c7j$18du$1@digitaldaemon.com>, Walter says...
>>
>>
>> "Derek Parnell" <derek@psych.ward> wrote in message
>> news:ce2ce5$ib8$1@digitaldaemon.com...
>>> 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.
>>
>> That's normal behavior, it's the integral argument promotion rules in C and
>> C++ which survives intact in D.
>>
>>> 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.
>>
>> But it's implicitly convertible to a double.
>
> I like the current D scheme.  In a sense, every class defines its own interface
> and calls are evaluated against this interface before walking the inheritance
> tree.  This safety is a good thing.  The alternative would be code that subtly
> breaks if a function prototype is added or altered somewhere up the inheritance
> tree.  I'm surprised that folks can argue that "override" should be mandatory
> and then argue that the current D lookup scheme is broken, since the goal of
> both things is to protect the programmer from subtle inheritance-based coding
> errors.

Requiring override protects you from doing this...

class X1 {
  void read(ubyte[] buf);
}

classes X2 thru X8

class X9 : X8 {
  void read(ubyte[] buf);
}

In other words accidently overriding a function in a base class with the _exact same_ function signature.


The problem with the lookup rules is _not_ that they do not find functions of the same signature, because they _do_, it's that they do not find functions of a different signature _without_ using alias. eg.

class X1 {
  void read(char[] buf);
}

classes X2 thru X8

class X9 : X8 {
  void read(ubyte[] buf);
}

void main()
{
  X9 p = new X9();
  char[] line;

  p.read(line);
}

> As far as the above example is concerned, perhaps this is an argument for the
> "explicit" keyword.  It would certainly reduce or eliminate much of the
> confusion about D's lookup rules.
>
>
> Sean
>
>



-- 
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
July 27, 2004
On Mon, 26 Jul 2004 22:15:32 +0000 (UTC), Sean Kelly <sean@f4.ca> wrote:
> All that aside, should this really
> be legal?
>
> interface I { void foo( char x ); }
> class A { void foo( char x ) {} }
> class B : A {}
> class C : B {}
> class D : C {}
> class E : D {}
> class F : E, I { void foo( int x ) {} }
>
> Assume that all the above classes have very long definitions and that A, B, and
> C are in a third-party library with no documentation and convoluted, badly
> formatted import files.  As the creator of F I may very well have intended to
> define F.foo as the implementation for I.foo and just screwed up the prototype.
> With the existing lookup scheme this is flagged as an error but with the
> proposed scheme it would compile just fine.  Further assume that A.foo is for
> some completely different purpose as A may have no knowledge of interface I.
> IMO the proposed scheme will be the cause of some very hard to find bugs.

I think a new keyword is required to show the programmers intent. i.e. "implement".
If you are writing a method to satisfy an interface you preceed it with "implement"

Using your example:

class A { void foo( char x ) {} }
class B : A {}
class C : B {}
class D : C {}
class E : D {}
class F : E, I { implement void foo( int x ) {} }

Would error saying "F.foo(int) does not implement any interface (I)"
If the user then changed it to the correct definition:
  class F : E, I { implement void foo( char x ) {} }

assuming override is mandatory, it would again error "F.foo(char) overrides A.foo(char)"
so the user now needs:
  class F : E, I { implement override void foo( char x ) {} }

This is _another_ argument _for_ a mandatory override keyword, _even if_ you don't agree a new implement keyword is a good idea.

Regan

-- 
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
July 27, 2004
On Mon, 26 Jul 2004 02:59:49 -0700, Walter wrote:

> "Derek Parnell" <derek@psych.ward> wrote in message news:ce2ce5$ib8$1@digitaldaemon.com...
>> 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.
> 
> That's normal behavior, it's the integral argument promotion rules in C and C++ which survives intact in D.

...and 'normal' is equivalent to 'desirable', right? It seems wrong to me that if I _explicitly_ code an integer that the compiler silently converts this to something else *before* checking for a matching method signature.

When I go to the beach and surf all day, its normal to get sand in my shorts, but it doesn't mean I like it.

>> 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.
> 
> But it's implicitly convertible to a double.

It's also implicitly convertible to many other things too! So why pick on 'double'?

>> 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.
> 
> That's a compiler bug :-(

Oh! Okay then.

>> 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.
> 
> It's not that bad. The following should work:
> 
> 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 X2.f f;
>              }
> class X4: X3 { void f(char x)  {printf("4\n");}
>                alias X3.f f;
>              }
> class X5: X4 { void f(double x){printf("5\n");}
>                 alias X4.f f;
>              }

Yep it does. That is a suitable reduction in typing.

> as the aliases become part of the names in a scope, and so get carried along with the other names with a subsequent alias. And I'd add that if one actually wrote such an inheritance graph like that, the aliases are a good thing because they show you *intended* to overload them based on names from another scope.

Okay, so I need to read

   alias /classX/./funcA/ /funcB/

in this context as

"Assume that all the methods called /funcA/ from /classX/ (and whatever it has aliased) have been typed into this class, but are called /funcB/ in this class."


>> 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.
> 
> You're right. But spelling mistakes tend to get picked up by the compiler and show up as "undefined identifier" messages. Languages that implicitly declare misspelled identifiers tend to be very hard languages to debug code in.

Agreed, but that's not what I was referring to. More like this ...

  void foo() { . . . };

  void fooo() {  . . . };

And the coder type in 'foo();' when they really meant 'fooo();'

The compiler (and coder) aint <g> necessarily going to notice this until
run time.

>To extend the analogy to the example, I'd rather have to proactively say
> I want to include the declarations from another scope (with an alias declaration) than have it happen implicitly with no way to turn it off, and subtle problems result. Think of the alias declaration like an explicit cast - it tells the compiler (and the maintenance programmer) that "yes, I intended to do this" and the compiler says "yes sir, three bags full sir" and adds the methods into the scope and does the cast.

It just that I would have thought that just by deriving a class from another, the coder is already explicitly saying that they intend to use the methods in the super-class. To me this seems a normal (there's that word again) thing to do. Otherwise it like saying "Derive classX from classY and use methods A, B, and D from classY, and not any others.". Whereas I tend to think along the lines of "Derive classX from classY except for methods C, E and F".

I will readjust my thinking, of course, as I don't expect D will change in this respect ;-)

-- 
Derek
Melbourne, Australia
27/Jul/04 11:05:01 AM