digitalmars.D - polymorphism: overloading vs. overriding
- Markus Kranz <Markus_member pathlink.com> May 07 2006
- Mike Parker <aldacron71 yahoo.com> May 07 2006
- markus_kranz gmx.net May 07 2006
- Bruno Medeiros <brunodomedeirosATgmail SPAM.com> May 07 2006
- Mike Parker <aldacron71 yahoo.com> May 07 2006
- Bruno Medeiros <brunodomedeirosATgmail SPAM.com> May 07 2006
- markus_kranz gmx.net May 07 2006
- Bruno Medeiros <brunodomedeirosATgmail SPAM.com> May 07 2006
- Markus Kranz <markus_kranz gmx.net> May 07 2006
- Bruno Medeiros <brunodomedeirosATgmail SPAM.com> May 07 2006
- Markus Kranz <markus_kranz gmx.net> May 07 2006
- Markus Kranz <markus_kranz gmx.net> May 07 2006
- Bruno Medeiros <brunodomedeirosATgmail SPAM.com> Jun 15 2006
Following the rule of last surprise: wouldn't it be desirable if the output of
the following program would be:
Bar.doIt()
Bar.doIt(Bar)
The actual output
Bar.doIt()
Foo.doIt(Foo)
at least for me is a little bit surprising since the best matching function
seems to be Bar.doIt(Bar).
As overriding with covariant return types is supported it seems a bit unnatural
to me that overriding with covariant arguments does not work.
Instead of doing overload resolution before virtual function resolution (what
seems to be done now) an implementation of 'covariant arguments' theoretically
could be as simple as reversing the order of resolution.
What do you think?
Regards,
Markus
--
import std.stdio;
class Foo {
void doIt() { writefln("Foo.doIt()"); }
void doIt(Foo that) { writefln("Foo.doIt(Foo)"); }
}
class Bar : Foo {
//overrides Foo.doIt()
void doIt() { writefln("Bar.doIt()"); }
//overloads (inherited) Foo.doIt(Foo)
void doIt(Bar that) { writefln("Bar.doIt(Bar)"); }
}
int main() {
Foo foo = new Bar();
foo.doIt();
foo.doIt(foo);
}
May 07 2006
Markus Kranz wrote:Following the rule of last surprise: wouldn't it be desirable if the output of the following program would be: Bar.doIt() Bar.doIt(Bar) The actual output Bar.doIt() Foo.doIt(Foo) at least for me is a little bit surprising since the best matching function seems to be Bar.doIt(Bar). As overriding with covariant return types is supported it seems a bit unnatural to me that overriding with covariant arguments does not work. Instead of doing overload resolution before virtual function resolution (what seems to be done now) an implementation of 'covariant arguments' theoretically could be as simple as reversing the order of resolution. What do you think?
This line is the key: Foo foo = new Bar(); foo is an instance of Foo, not an instance of Bar, so the appropriate method (Foo.doIt(Foo)) is being called. Java and C++ both have the same behavior. What you are suggesting is unintuitive. Not all Foos are Bars, but all Bars are Foos. If you want Bar.doIt to be called when you have a an instance of Foo, you have to downcast.
May 07 2006
In article <e3ki2e$2o7a$1 digitaldaemon.com>, Mike Parker says...Markus Kranz wrote:[The desired output] Bar.doIt() Bar.doIt(Bar) The actual output Bar.doIt() Foo.doIt(Foo) [snip]
This line is the key: Foo foo = new Bar(); foo is an instance of Foo, not an instance of Bar, so the appropriate method (Foo.doIt(Foo)) is being called.
Sorry, but what I read is that foo is not just a Foo but even a Bar?!Java and C++ both have the same behavior.
I can confirm this at least for Java. But someone told me D was not Java... ;-)What you are suggesting is unintuitive. Not all Foos are Bars, but all Bars are Foos. If you want Bar.doIt to be called when you have a an instance of Foo, you have to downcast.
To my knowledge in D as in Java all member functions are virtual. That is why foo.doIt() actually calls the version in Bar - without any explicit cast. Why do you think the actual behaviour is intuitive while my proposal isn't? I would have understood if Foo.doIt() Foo.doIt(Foo) got your vote. Regards Markus
May 07 2006
Mike Parker wrote:Markus Kranz wrote:Following the rule of last surprise: wouldn't it be desirable if the output of the following program would be: Bar.doIt() Bar.doIt(Bar) The actual output Bar.doIt() Foo.doIt(Foo) at least for me is a little bit surprising since the best matching function seems to be Bar.doIt(Bar). As overriding with covariant return types is supported it seems a bit unnatural to me that overriding with covariant arguments does not work. Instead of doing overload resolution before virtual function resolution (what seems to be done now) an implementation of 'covariant arguments' theoretically could be as simple as reversing the order of resolution. What do you think?
This line is the key: Foo foo = new Bar(); foo is an instance of Foo, not an instance of Bar,
No, that is wrong, foo *is* an instance of Bar. (yet, it's type contract is only that it is a Foo) -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
May 07 2006
Bruno Medeiros wrote:Mike Parker wrote:This line is the key: Foo foo = new Bar(); foo is an instance of Foo, not an instance of Bar,
No, that is wrong, foo *is* an instance of Bar. (yet, it's type contract is only that it is a Foo)
Yes, yes. Underneath the foo is an instance of Bar. But because, as you say, the type contract is that of Foo then you are effectively dealing with a Foo and not a Bar. It should only be treated as a Bar when you downcast.
May 07 2006
Markus Kranz wrote:Following the rule of last surprise: wouldn't it be desirable if the output of the following program would be: Bar.doIt() Bar.doIt(Bar) The actual output Bar.doIt() Foo.doIt(Foo) at least for me is a little bit surprising since the best matching function seems to be Bar.doIt(Bar). As overriding with covariant return types is supported it seems a bit unnatural to me that overriding with covariant arguments does not work. Instead of doing overload resolution before virtual function resolution (what seems to be done now) an implementation of 'covariant arguments' theoretically could be as simple as reversing the order of resolution. What do you think? Regards, Markus -- import std.stdio; class Foo { void doIt() { writefln("Foo.doIt()"); } void doIt(Foo that) { writefln("Foo.doIt(Foo)"); } } class Bar : Foo { //overrides Foo.doIt() void doIt() { writefln("Bar.doIt()"); } //overloads (inherited) Foo.doIt(Foo) void doIt(Bar that) { writefln("Bar.doIt(Bar)"); } } int main() { Foo foo = new Bar(); foo.doIt(); foo.doIt(foo); }
No, it's not unnatural. For a function to override another, the return type must be covariant, but the parameters must be contravariant. It's quite natural to be this way, in fact, it's the only correct way (other than invariant parameters). The reason why it is so, is because for a function to override another, it must handle all (or more) inputs than the previous function. Consider, in your example, the following addition class Baz : Foo { } Foo.doIt(Foo that) can handle an argument of type Baz, but Bar.doIt(Bar that) cannot handle such an argument. Thus Bar.doIt doesn't override Foo.doIt -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
May 07 2006
In article <e3kjf0$2q09$3 digitaldaemon.com>, Bruno Medeiros says...Markus Kranz wrote:[snip] -- import std.stdio; class Foo { void doIt() { writefln("Foo.doIt()"); } void doIt(Foo that) { writefln("Foo.doIt(Foo)"); } } class Bar : Foo { //overrides Foo.doIt() void doIt() { writefln("Bar.doIt()"); } //overloads (inherited) Foo.doIt(Foo) void doIt(Bar that) { writefln("Bar.doIt(Bar)"); } } int main() { Foo foo = new Bar(); foo.doIt(); foo.doIt(foo); }
No, it's not unnatural. For a function to override another, the return type must be covariant, but the parameters must be contravariant. It's quite natural to be this way, in fact, it's the only correct way (other than invariant parameters). The reason why it is so, is because for a function to override another, it must handle all (or more) inputs than the previous function. Consider, in your example, the following addition class Baz : Foo { } Foo.doIt(Foo that) can handle an argument of type Baz, but Bar.doIt(Bar that) cannot handle such an argument. Thus Bar.doIt doesn't override Foo.doIt
I see Bar.doIt(Bar) does not override Foo.doIt(Foo) given your definition of overriding. But given your szenario: Bar bar = new Bar(); Baz baz = new Baz(); bar.doIt(baz); AFAIS should work perfectly (cause baz is a Baz and so a Foo and Bar inherits from Foo the function doIt(Foo))... ..and does so in Java (but that's no argument <g>) ..but fails in D <hmpf> test.d(23): function test.Bar.doIt () does not match argument types (Baz) test.d(23): cannot implicitly convert expression (baz) of type test.Baz to test.Bar Of course what dmd says is true, but why can't it implicitly convert expression (baz) of type test.Baz to test.Foo? Does 'overriding' in D does not mean the same as in Java? Regards, Markus
May 07 2006
markus_kranz gmx.net wrote:In article <e3kjf0$2q09$3 digitaldaemon.com>, Bruno Medeiros says...Markus Kranz wrote:[snip] -- import std.stdio; class Foo { void doIt() { writefln("Foo.doIt()"); } void doIt(Foo that) { writefln("Foo.doIt(Foo)"); } } class Bar : Foo { //overrides Foo.doIt() void doIt() { writefln("Bar.doIt()"); } //overloads (inherited) Foo.doIt(Foo) void doIt(Bar that) { writefln("Bar.doIt(Bar)"); } } int main() { Foo foo = new Bar(); foo.doIt(); foo.doIt(foo); }
type must be covariant, but the parameters must be contravariant. It's quite natural to be this way, in fact, it's the only correct way (other than invariant parameters). The reason why it is so, is because for a function to override another, it must handle all (or more) inputs than the previous function. Consider, in your example, the following addition class Baz : Foo { } Foo.doIt(Foo that) can handle an argument of type Baz, but Bar.doIt(Bar that) cannot handle such an argument. Thus Bar.doIt doesn't override Foo.doIt
I see Bar.doIt(Bar) does not override Foo.doIt(Foo) given your definition of overriding. But given your szenario: Bar bar = new Bar(); Baz baz = new Baz(); bar.doIt(baz); AFAIS should work perfectly (cause baz is a Baz and so a Foo and Bar inherits from Foo the function doIt(Foo))... ...and does so in Java (but that's no argument <g>) ...but fails in D <hmpf> test.d(23): function test.Bar.doIt () does not match argument types (Baz) test.d(23): cannot implicitly convert expression (baz) of type test.Baz to test.Bar
In http://www.digitalmars.com/d/function.html , "Function Inheritance and Overriding" it is said: "However, when doing overload resolution, the functions in the base class are not considered:" If that is the ideal behavior, well, that I'm not sure...Of course what dmd says is true, but why can't it implicitly convert expression (baz) of type test.Baz to test.Foo?
Even if you put a cast(Foo) on the argument, it won't compile, because it only considers the overloads (Bar.doIt) of the child class (Bar) Once again, if that is the ideal behavior, I'm not sure...Does 'overriding' in D does not mean the same as in Java?
-- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
May 07 2006
In article <e3knre$2vp1$1 digitaldaemon.com>, Bruno Medeiros says...In http://www.digitalmars.com/d/function.html , "Function Inheritance and Overriding" it is said: "However, when doing overload resolution, the functions in the base class are not considered:" If that is the ideal behavior, well, that I'm not sure...Of course what dmd says is true, but why can't it implicitly convert expression (baz) of type test.Baz to test.Foo?
Even if you put a cast(Foo) on the argument, it won't compile, because it only considers the overloads (Bar.doIt) of the child class (Bar) Once again, if that is the ideal behavior, I'm not sure...
Okay. At least the considered alias construction works.Does 'overriding' in D does not mean the same as in Java?
That's soothing. Regards Markus
May 07 2006
Bruno Medeiros wrote:No, it's not unnatural. For a function to override another, the return type must be covariant, but the parameters must be contravariant. It's quite natural to be this way, in fact, it's the only correct way (other than invariant parameters).
Whoa, I forgot to mention: D doesn't support contravariant parameters, only covariant return types (in other words, a covariant parameter will create a new overload, and not an override). I don't know why I got the idea that D supported that. -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
May 07 2006
In article <e3kp8d$31ag$1 digitaldaemon.com>, Bruno Medeiros says...Bruno Medeiros wrote:No, it's not unnatural. For a function to override another, the return type must be covariant, but the parameters must be contravariant. It's quite natural to be this way, in fact, it's the only correct way (other than invariant parameters).
Whoa, I forgot to mention: D doesn't support contravariant parameters, only covariant return types (in other words, a covariant parameter will create a new overload, and not an override). I don't know why I got the idea that D supported that.
So the actual state is (1) the scope of overload resolution is the current class (and optionally superclasses using alias) (2) the scope of virtual function resolution is the current class and 'childwards' Overloading and overriding on their own seem to be simple concepts. It remains the question: as in D is it enough to implement both concepts separately although they are strongly related? (In both concepts we deal with multiple functions having a common name and search for the 'best' matching instance.) Regards, Markus
May 07 2006
Uh, I didn't know the issue of "covariance versus contravariance" is a topic of continuing debate: http://c2.com/cgi/wiki?ContraVsCoVariance and especially http://www.cs.trinity.edu/~mlewis/CSCI3294-F01/Papers/p431-castagna.pdf where I found on page 10: "At this point, we are able to make precise the roles played by covariance and contravariance in subtyping: contravariance is the correct rule when you want to substitute a function of a given type for another one of a different type; covariance is the correct condition when you want to specialize (in object-oriented jargon "override") a branch of an overloaded function by one with a smaller input type." Regards, Markus
May 07 2006
Markus Kranz wrote:Uh, I didn't know the issue of "covariance versus contravariance" is a topic of continuing debate: http://c2.com/cgi/wiki?ContraVsCoVariance and especially http://www.cs.trinity.edu/~mlewis/CSCI3294-F01/Papers/p431-castagna.pdf where I found on page 10: "At this point, we are able to make precise the roles played by covariance and contravariance in subtyping: contravariance is the correct rule when you want to substitute a function of a given type for another one of a different type; covariance is the correct condition when you want to specialize (in object-oriented jargon "override") a branch of an overloaded function by one with a smaller input type." Regards, Markus
I finally took the time to read it, it is indeed a good article. -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Jun 15 2006









markus_kranz gmx.net 