Design Decision: should types be declared statically?
Java includes static type declarations. Smalltalk does not. They are optional in Cecil.
Design Decision: should abstract type and concrete implementation inheritance be separate or combined?
Reasons for separating them:
abstract type Stack implementation classes ListStack, ArrayStack both would conform to the abstract type StackExample of inheriting implementation but not abstract type:
Stack inherits from Deque (just masks off extra operations like front)
In Java, types can be specified using classes, abstract classes, or interfaces. You can't mask off extra operations. This seems like a good tradeoff among the alternatives.
Another definition sometimes used in type inference systems: a type is a set of classes (see e.g. Palsberg and Schwartzbach)
The contravariant rule for subtyping is used in Emerald, Cecil, Trellis/Owl, etc. (It is also the rule used in Cardelli's paper "A Semantics of Multiple Inheritance".)
The covariant rule is used in Eiffel, and in Java for arrays (as a special case).
The invariant rule is used in Java for everything but arrays.
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 invariant 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. (This is analogous to the Java array typing example previously.)
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 invariant 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 invariant 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 invariant 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) { } }