The contravariant rule for subtyping is used in Emerald, Cecil, Trellis/Owl, and other languages.
The covariant rule is used in Eiffel, and in Java for arrays (as a special case).
The equivariant rule is used in Java for everything but arrays. (Also sometimes called the invariant rule.)
Type Color Type GrayScaleColor (a subtype of Color) Type Number subtypes Float, Integer // in this example I'm assuming numbers are full-fledged objects Type Point x(): Number y(): Number Type ColoredPoint x(): Number y(): Number mycolor() : Color Type GrayScalePoint x(): Number y(): Number mycolor() : GrayScaleColorUnder all of the rules, ColoredPoint is a subtype of Point -- anywhere a Point is needed, we can use a ColoredPoint.
Under the contravariant and covariant rules, GrayScalePoint is a subtype of ColoredPoint Point -- anywhere a ColoredPoint is needed, we can use a GrayScalePoint. However, the equivariant rule says that GrayScalePoint is not a subtype of ColoredPoint, because mycolor has a different signature in the two types.
Example code:
p : ColoredPoint; c : Color p := new GrayScalePoint(); c := p.mycolor; q , r : Point; a : Number; q := new ColoredPoint(); r := new GrayScalePoint(); a := q.x + r.x;Example with a method with an argument:
Type Point x(): Number y(): Number setDotSize(c : Integer); Type ColoredPoint x(): Number y(): Number setDotSize(c : Number);Observe the argument type for setDotSize. The contravariant rule says that ColoredPoint is still a subtype of Point.
p1, p2 : Point; c : ColoredPoint; p1 := new Point(); c := new ColoredPoint(); p2 := c; // legal because ColoredPoint is a kind of Point p1.setDotSize(3); p2.setDotSize(4); c.setDotSize(4.5); // legal because a float is a kind of numberHowever, the following statements would give compile-time errors:
p1.setDotSize(3.5); p2.setDotSize(3.5); // ok in dynamically typed languageHowever, in a dynamically typed language, the first statement would still fail, but the second would be OK.
Now consider:
Type ColoredPoint x(): Number y(): Number mycolor() : Color setcolor(c : Color); Type GrayScalePoint x(): Number y(): Number mycolor() : GrayScaleColor setcolor(c : GrayScaleColor);Under the contravariant rule there is no longer a subtype relation between ColoredPoint and GrayScalePoint. Example:
c : ColoredPoint; c := new GrayScalePoint(); /* compile time error */ c.setcolor(yellow); /* an error would occur if we tried this */
However, under the covariant rule GrayScalePoint would still be a subtype of ColoredPoint, and the above program would type check correctly, and in the absence of a runtime type check, would lead to errors.
class Point { public int x; public int y; } class ColoredPoint extends Point { public Color mycolor; } interface PointMaker { Point makePoint(); } interface PointEater { void eat(Point p); } interface ColoredPointMaker { ColoredPoint makePoint(); } interface ColoredPointEater { void eat(ColoredPoint p); }The following alternate declaration for ColoredPointMaker would be illegal:
interface ColoredPointMaker extends PointMaker { ColoredPoint makePoint(); }The following alternate declaration for ColoredPointEater is OK, and says that a ColoredPointEater must provide two different eat methods:
interface ColoredPointEater extends PointEater { void eat(ColoredPoint p); }Both these declarations are legal:
class PtMaker1 implements PointMaker { Point makePoint() { return new Point(); } } class PtMaker2 implements PointMaker { Point makePoint() { return new ColoredPoint(); } }The following is illegal under Java's equivariant type rule. (It would be legal under the covariant and contravariant rules.)
class PtMaker3 implements PointMaker { ColoredPoint makePoint() { return new ColoredPoint(); } }The following is also illegal under Java's equivariant type rule. (It would be legal under the contravariant rule, but not the covariant rule. It would be type safe.)
class Eater1 implements ColoredPointEater { void eat (Point p) { } }Finally, the following is illegal under Java's equivariant type rule. (It would be legal under the covariant rule, but not the contravariant rule. It would not be type safe unless there were a runtime check.)
class Eater2 implements PointEater { void eat (ColoredPoint p) { } }
if X extends Y then also X[] extends Y[]
This version of the subtyping relation uses the covariant rule, and is actually unsound! So Java introduces a runtime check, as described in the Notes on Pizza.