Question

I just learned in my programming languages class that "contravariant argument types would actually be safe, but they have not been found useful and are hence not supported in practical languages." Even though they are not supported, I am confused as to why something like this example we were given would still be, in theory, "safe":

class Animal {
  ...
  public bool compare(Panda) { ... }
} 

class Panda extends Animal {
  ... 
  public bool compare(Animal) { ... }
}

From what I understand, problems with subtyping come up when something is done that could cause a loss of specificity. So what if I did this? :

Panda p = new Panda(); 
Animal a = new Animal
...
p.compare(a); 

When I look at this, it seems like panda could (and probably does) have some extra fields in it that a plain animal wouldn't know about. Thus, even if all of their animal-specific data members are the same, a panda can have other stuff that differs. How would that make it okay to compare it to a plain animal? Would it just consider the animal-only stuff and ignore the rest?

Was it helpful?

Solution

In your example you don't use any generic types. You have Panda extending Animal, and it's an example of inheritance and leads to polymorphism which is more or less what you describe. Check the links.

To get contravariance, you need to consider some generic type. I'll use .NET type IComparer`1[T] as an example. With C# syntax (which I'll use rather than Java), we indicate that IComparer is contravariant in T by writing in in the definition:

public interface IComparer<in T>
{
  ...
}

Suppose I have a method which returns an IComparer`1[Animal] (or IComaparer<Animal>), like:

static IComparer<Animal> CreateAnimalComparer()
{
  // code that returns something here
}

Now in C#, it's legal to say:

IComparer<Panda> myPandaComparer = CreateAnimalComparer();

Now, this is because of contravariance. Note that the type IComparer<Animal> does not derive from (or "extend") the type IComparer<Panda>. Instead, Panda derives from Animal, and this leads to the IComparer<Xxxx> being assignable to each other (in the opposite order, hence "contravariance" (not "covariance")).

The reason why it's meaningful to declare a Comparer<> contravariant, is if you have a comparer that can compare two arbitrary animals, and return a signed number indicating which is greater, then that same comparer can also take in two pandas and compare those. For pandas are animals.

So the relation

any Panda is an Animal

(from inheritance) leads to the relation

any IComparer<Animal> is an IComparer<Panda>

(by contravariance).

For an example with covariance, the same relation

any Panda is an Animal

leads to

any IEnumerable<Panda> is an IEnumerable<Animal>

by covariance (IEnumerable<out T>).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top