digitalmars.D - Plain old covariance and contravariance
- Reiner Pope (19/19) Oct 17 2006 I did some searching for variance here, and I couldn't find much
- Hasan Aljudy (7/33) Oct 17 2006 I think that's because Bar.set can also accept invalid parameters, such
- BCS (55/81) Oct 17 2006 Why would that be a problem?
- =?ISO-8859-1?Q?Jari-Matti_M=E4kel=E4?= (3/47) Oct 17 2006 It's better to use naming conventions to avoid these kind of semantic
- BCS (4/15) Oct 18 2006 Yes that is the first solution, but some times that isn't an option,
- =?ISO-8859-1?Q?Jari-Matti_M=E4kel=E4?= (7/23) Oct 18 2006 Yeah, I know. But still a "proper" solution would need some extra
- Bruno Medeiros (12/72) Oct 18 2006 Yes, that's the reason. An object can be *converted* to an interface and...
I did some searching for variance here, and I couldn't find much discussion of it. My question (I think) is quite simple: why is covariance supported but not contravariance? interface Foo { Foo get(); void set(Bar x); } interface Baz : Foo {} class Bar : Foo { Bar get() {} // Fine void set(Foo x) {} // Error, doesn't implement Foo.set } For some reason, Bar.set isn't taken to be the implementation of Foo.set, even though it will work for any parameters that Foo.set does. What am I missing? Cheers, Reiner
Oct 17 2006
Reiner Pope wrote:I did some searching for variance here, and I couldn't find much discussion of it. My question (I think) is quite simple: why is covariance supported but not contravariance? interface Foo { Foo get(); void set(Bar x); } interface Baz : Foo {} class Bar : Foo { Bar get() {} // Fine void set(Foo x) {} // Error, doesn't implement Foo.set } For some reason, Bar.set isn't taken to be the implementation of Foo.set, even though it will work for any parameters that Foo.set does. What am I missing? Cheers, ReinerI think that's because Bar.set can also accept invalid parameters, such as X, where X extends Foo. class X : Foo { ... }
Oct 17 2006
Hasan Aljudy wrote:Reiner Pope wrote:Why would that be a problem? Bar.set can only be called from an instance of Foo or Bar. If it is called by way of Foo, then the parameter will be a Bar, or something derived from Bar (all of which are also Foo s) and thus no problem. If it is called by way of Bar, then the parameter will be a Foo (or Foo derived), again, no problem. One potential issue is that class references and interfaces might not be treated quite the same (I am currently doing the homework on this one so I may be wrong). However even getting rid of that doesn't work. interface I { void set(B); // all B's are A's } class A{} class B:A{} class C:I { // any B can be used void set(A a){} } I would expect that the issue has something to do with the overload resolution rules (it might make the rules more complicated). <rant type="soapbox"> One fix for this would be to allow explicit mappings. class C:I { void set(A a){} // quick and dirty syntax (not intended for final product) alias { I.set(B) = set(A); } } This would also allow a single class to implement several (3rd party) interface that have methods with the same signature but different semantics. interface Critic { //returns true if Critic approves of Subject bool Opinion(Subject); //return a value indicating how much to trust critic real Weight(); } interface PhyObject { //returns height in feet real Height(); //return weight in lb real Weight(); } class Person : PhyObject, Critic { real Weight() // Now what? } </rant>interface Foo { Foo get(); void set(Bar x); } interface Baz : Foo {} class Bar : Foo { Bar get() {} // Fine void set(Foo x) {} // Error, doesn't implement Foo.set }I think that's because Bar.set can also accept invalid parameters, such as X, where X extends Foo. class X : Foo { ... }
Oct 17 2006
BCS wrote:<rant type="soapbox"> One fix for this would be to allow explicit mappings. class C:I { void set(A a){} // quick and dirty syntax (not intended for final product) alias { I.set(B) = set(A); } } This would also allow a single class to implement several (3rd party) interface that have methods with the same signature but different semantics. interface Critic { //returns true if Critic approves of Subject bool Opinion(Subject); //return a value indicating how much to trust critic real Weight(); } interface PhyObject { //returns height in feet real Height(); //return weight in lb real Weight(); } class Person : PhyObject, Critic { real Weight() // Now what? } </rant>It's better to use naming conventions to avoid these kind of semantic collisions.
Oct 17 2006
Jari-Matti Mäkelä wrote:BCS wrote:[...]This would also allow a single class to implement several (3rd party) interface that have methods with the same signature but different semantics.It's better to use naming conventions to avoid these kind of semantic collisions.Yes that is the first solution, but some times that isn't an option, consider using 3rd party, closed source libs.
Oct 18 2006
BCS wrote:Jari-Matti Mäkelä wrote:Yeah, I know. But still a "proper" solution would need some extra semantical information attached to the method signature in some way or another. I have not done any large scale commercial (~=closed source) development, but even in open source solutions you very often have to break down objects that implement several interfaces into several classes.BCS wrote:[...]This would also allow a single class to implement several (3rd party) interface that have methods with the same signature but different semantics.It's better to use naming conventions to avoid these kind of semantic collisions.Yes that is the first solution, but some times that isn't an option, consider using 3rd party, closed source libs.
Oct 18 2006
BCS wrote:Hasan Aljudy wrote:Yes, that's the reason. An object can be *converted* to an interface and vice-versa, but they are not covariant with each other, precisely because of that conversion (the pointer changes). It's not just an opaque cast (aka paint cast?). The reason covariant return types work for classes<->interfaces nonetheless is because some special workarounds are put in place to convert when necessary between function calls/returns.Reiner Pope wrote:Why would that be a problem? Bar.set can only be called from an instance of Foo or Bar. If it is called by way of Foo, then the parameter will be a Bar, or something derived from Bar (all of which are also Foo s) and thus no problem. If it is called by way of Bar, then the parameter will be a Foo (or Foo derived), again, no problem. One potential issue is that class references and interfaces might not be treated quite the same (I am currently doing the homework on this one so I may be wrong).interface Foo { Foo get(); void set(Bar x); } interface Baz : Foo {} class Bar : Foo { Bar get() {} // Fine void set(Foo x) {} // Error, doesn't implement Foo.set }I think that's because Bar.set can also accept invalid parameters, such as X, where X extends Foo. class X : Foo { ... }However even getting rid of that doesn't work. interface I { void set(B); // all B's are A's } class A{} class B:A{} class C:I { // any B can be used void set(A a){} } I would expect that the issue has something to do with the overload resolution rules (it might make the rules more complicated).Hum, but that should work. Maybe it's a bug. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Oct 18 2006