Thread overview
Plain old covariance and contravariance
Oct 17, 2006
Reiner Pope
Oct 17, 2006
Hasan Aljudy
Oct 17, 2006
BCS
Oct 18, 2006
BCS
Oct 18, 2006
Bruno Medeiros
October 17, 2006
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
October 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,
> 
> Reiner

I think that's because Bar.set can also accept invalid parameters, such as X, where X extends Foo.

class X : Foo
{
 ...
}
October 17, 2006
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>
October 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.
October 18, 2006
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
October 18, 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.
October 18, 2006
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.