Say we have the following types:

`A`

, `B`

, `A1`

and `B1`

, where `A1`

is a subtype of `A`

, and `B1`

is a subtype of `B`

. In Java it would look like this:

class A {

...

}

class A1 extends A {

...

}

class B {

...

}

class B1 extends B {

...

}

Now let's build a class of pairs:

class Pair<X, Y> {

X first;

Y second;

}

If we compare

`Pair<A, B> ab; `

and `Pair<A1, B1> a1b1; `

we see that it is natural to expect that a1b1 should be assignable to ab, but not vice versa: the components can be cast in one direction only. `A1`

is a *specialization*of

`A`

; `B1`

is *specialization*of

`B`

, and so the `Pair<A1,B1>`

is a specialization of `Pair<A,B>`

. This is what is called covariance: specialization transforms into specialization. And if you are a fan of "Liskov's substitution principle", you can see that a

`Pair<A1, B1>`

can always be used ("substitute") where a `Pair<A, B>`

is required.Now the opposite case, contravariance. Suppose we have two functions declared like this:

B f(A a);

B f1(A1 a1);

(I use Java syntax; the two lines above mean that both functions return B).

Which one can replace which? See,

`f1`

cannot be used instead of `f`

: what if we pass something that is not `A1`

? `f1`

does not know what to do with it. But the opposite is okay: we can always use `f`

where `f1`

is required: any `A1`

can be viewed as an `A`

.When we deal with object methods, not just plain functions, one hidden parameter is always present: the object itself. So that a method declared as

class A {

...

C f(B b);

...

}

actually is a function defined on pairs (A, B), and takes values in C. This function is contravariant in both parameters. Contravariance in the first (hidden) parameter is an essential part of OOP. Take a look at the following code snippet:

class A1 extends A {

...

C f(B b) {

println(getClass().getName() + " called with " + b.getClass().getName());

}

}

...

A instanceOfA = new A1();

...

instanceOfA.f(b);

...

}

This is Java, and Java uses dynamic dispatch (that is, all methods are virtual), and so the method of

`A1`

is called *in place*of method f of

`A`

. Method `f`

of `A`

is substituted with a method of `A1`

; contravariance made it possible.Note that, since a method is always contravariant on its owner object, all getters, for instance, are contravariant: a superclass can call them, and the owner object, which is an instance of a subclass, can

*substitute*the superclass in any context.

(Many thanks to M.Abadi and L.Cardelli's "A Theory of Objects" for explaining the OOP part to me.)

## No comments:

Post a Comment