www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Plain old covariance and contravariance

reply Reiner Pope <reiner.pope REMOVE.THIS.gmail.com> writes:
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
parent reply Hasan Aljudy <hasan.aljudy gmail.com> writes:
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,
 
 Reiner

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
parent reply BCS <BCS pathlink.com> writes:
Hasan Aljudy wrote:
 
 
 Reiner Pope wrote:
 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 { ... }

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>
Oct 17 2006
next sibling parent reply =?ISO-8859-1?Q?Jari-Matti_M=E4kel=E4?= <jmjmak utu.fi.invalid> writes:
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
parent reply BCS <BCS pathlink.com> writes:
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
parent =?ISO-8859-1?Q?Jari-Matti_M=E4kel=E4?= <jmjmak utu.fi.invalid> writes:
BCS wrote:
 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.

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.
Oct 18 2006
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
BCS wrote:
 Hasan Aljudy wrote:
 Reiner Pope wrote:
 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 { ... }

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).

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.
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