Pizza is implemented as a pre-processor. Two different translation schemes:
In the rest of these notes, we'll just look at parametric polymorphism (including some background information on subtyping rules).
We had problems, though, when we wanted to have things like a HashSet of Points -- unless we implement a different class of PointHashSet, all Java's type system will tell us is that the HashSet holds Objects.
class Pair<elem> { elem x; elem y; Pair (elem x, elem y) {this.x = x; this.y = y;} void swap () {elem t = x; x = y; y = t;} } Pair<String> p = new Pair("world!", "Hello,"); p.swap(); System.out.println(p.x + p.y); Pair<int> q = new Pair(22,64); q.swap; System.out.println(q.x - q.y);Suppose we tried to write this in ordinary Java:
class Pair { Object x; Object y; /* won't work with primitive types */ Pair (Object x, Object y) {this.x = x; this.y = y;} void swap () {Object t = x; x = y; y = t;} } Pair p = new Pair("world!", "Hello,"); p.swap(); /* this doesn't work - lost the type info */ System.out.println(p.x + p.y); /* do this instead: */ System.out.println((String)p.x + (String)p.y); /* this doesn't work because 22 is an int, which is a primitive type */ Pair q = new Pair(22,64); q.swap; System.out.println(q.x - q.y); /* TYPE ERROR HERE */
swap:: (*,*)->(*,*) swap (x,y) = (y,x) p = ("world,"Hello,") ps = swap p
class Pair_String { String x; String y; Pair_String (String x, String y) {this.x = x; this.y = y;} void swap () {String t = x; x = y; y = t;} } class Pair_int { int x; int y; Pair_int (int x, int y) {this.x = x; this.y = y;} void swap () {int t = x; x = y; y = t;} } Pair_String p = new Pair_String("world!", "Hello,"); p.swap(); System.out.println(p.x + p.y); Pair_int q = new Pair_int(22,64); q.swap; System.out.println(q.x - q.y);
class Pair { Object x; Object y; Pair (Object x, Object y) {this.x = x; this.y = y;} void swap () {Object t = x; x = y; y = t;} } Pair p = new Pair((Object)"world!", (Object)"Hello,"); /* above casts are not actually needed */ p.swap(); System.out.println((String)p.x + (String)p.y); Pair q = new Pair((Object) new Integer(22), (Object) new Integer(64)); q.swap; System.out.println(((Integer)(q.x)).intValue() - ((Integer)(q.y)).intValue();)
interface Pretty { String prettyprint(); } class Pair<elem implements Pretty> { elem x; elem y; Pair (elem x, elem y) {this.x = x; this.y = y;} String printboth () {return x.pretty() + y.pretty()}; }If we just declared the fields x and y to be of type Pretty then that's all we would know about the type of p.x -- but using bounded parameteric polymorphism we can give variables more specific types, such as Pair<SchemeCode> (where SchemeCode is a class that implements the Pretty interface).
/* Ord is an interface for objects that can be ordered. It declares a less method that is used to compare two objects. However, we only want to compare OrdInts with OrdInts and OrdStrings with OrdStrings, but not OrdInts with OrdStrings. */ interface Ord<elem> { boolean less(elem o); } class Pair<elem implements Ord<elem>> { elem x; elem y; Pair (elem x, elem y) {this.x = x; this.y = y;} elem min() {if (x.less(y)) return x; else return y;} } class OrdInt implements Ord<OrdInt> { int i; OrdInt (int i} {this.i = i;} int intValue() {return i;} public boolean less(OrdInt o) {return i < o.intValue()} } Pair<OrdInt> p = new Pair( new OrdInt(22), new OrdInt(64)); System.out.println(p.min().intValue());
/* this version allows OrdInts to be compared with OrdStrings */ interface Ord { boolean less(Ord o); } class OrdInt implements Ord { int i; OrdInt (int i} {this.i = i;} int intValue() {return i;} public boolean less(Ord o) {return i < o.intValue()} }
And in this version, OrdInt doesn't implement the Ord interface (according to Java's type rules).
... public boolean less(OrdInt o) {return i < o.intValue()} ...Ha! You say. The problem is obviously that the stupid Java designers made an overly restricted rule about types for methods. Well, no. If the above version of OrdInt was allowed to implement the Ord interface, then Java's type system would be unsound. Consider:
Ord x = new OrdString("octopus"); Ord y = new OrdInt(42); boolean b = x.less(y);
String[] s = {"squid","clam"}; Object[] x; x = s; /* legal since String[] is a subtype of Object[] */ x[0] = new Beachball(); /* we get a runtime exception here */ /* if we had allowed the beachball to get into our array of strings, the following statement would be using + for beachballs: */ System.out.println(s[0]+s[1]);
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 equivariant rule is used in Java for everything but arrays.
class ObjectArray { Object get(int i) { ...} set(int i, Object x) { ...} } class StringArray { String get(int i) { ...} set(int i, String x) { ...} }For arrays, Java behaves as if StringArray is a subtype of ObjectArray - but this is not sound. Consider the analog of the squid/beachball example:
StringArray s = new StringArray(); ObjectArray x; x = s; s.put(0,"squid"); s.put(0,"clam"); x.put(0,new Beachball()); /* BAD!! */ /* the following statement is using + for beachballs: */ System.out.println(s.get(0) + s.get(1));
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) { } }