Object a; Point p = new Point(10,20); Point q; a = p; // legal since a point is a kind of an object q = a; // NOT LEGAL (since the compiler only knows that a is of type Object)Similarly, if we had a method that expects an object as a parameter, we can pass it a Point, a String, or any other object.
[a] -> [a] -> [a](the type of the append function).
The user can't declare types of this sort in Java. This leads to less precise type declarations, less machine-checkable documentation, and more runtime casts. For example, the built-in collection classes in Java (Set, ArrayList, etc) all have Object as the type of the contents -- you can't declare that x is a set of strings, or a set of points.
For example, consider the following Java code fragment.
ArrayList a = new ArrayList(); Point p = new Point(10,20); Point q; // add the point p to the arraylist a.add(1,p); // now get the point out of the arraylist and assign it to q // bleah! I know perfectly well that I've only put points into a, but // Java's type system says that the type of a.get(i) is Object. So I // need to put in a cast and have it do a runtime check. q = (Point) a.get(1);
Point[] a = new Point[10]; Point p = new Point(10,20); Point q; a[0] = p; q = a[0]; // no cast needed - we know a[0] is a PointAlas, Java's type checking for arrays is unsound! Bleah again! However, the Java designers were well aware of this problem, and so Java includes a runtime check (so you get an exception, not a crash). Consider:
import java.awt.Point; class ArrayException { public static void main(String[] args) { String[] s; s = new String[10]; s[0] = "hi there"; test(s); } public static void test(Object[] a) { System.out.println("in test - before storing into a"); a[1] = new Point(10,20); System.out.println("in test - after storing into a"); } }This gives the following result:
in test - before storing into a Exception in thread "main" java.lang.ArrayStoreException at ArrayException.test(ArrayException.java:16) at ArrayException.main(ArrayException.java:11)So there is even a specialized kind of exception for this situation.
The technical term for the kind of type checking rule that Java uses for arrays (and that has this unsoundness problem) is covariant typing. Two other flavors of type rule are contravariant typing and equivariant typing. These are both sound. (These terms are defined in an optional set of notes on Subtypes in object-oriented languages.)
It looks like GJ-style parametric polymorphism will be supported in a future version of Java itself.
class Cell<A> { A contents; // the constructor Cell(A contents) { this.contents = contents; } void set(A contents) { this.contents = contents; } A get() { return contents; } }
Cell<String> a = new Cell("I'm a string!"); Cell<int> b = new Cell(17+4); b.set(9); int i = b.get(); String s = a.get();
public class TwoTypes<A, B> { public A first; public B second; public TwoTypes(A first, B second) { this.first = first; this.second = second; } }
TwoTypes<String, int> getValues() { return new TwoTypes(aString, anInt); }
Interface to sets of elements of a parameterised type:
interface Set<A> { boolean contains(A x); Set<A> union(Set<A> s); }
Heterogenous translation: specialized copy of code for each type (faster, more code)
Homogeneous translation: one copy of code (slower due to wrapping and run-time checks, less code)
First, here is an obvious but unfortunately wrong approach.
interface Ord { boolean less(Ord o); } class OrdInt implements Ord { private int i; public int intValue() {return i;} public boolean less(OrdInt o) {return i < o.intValue();} }Alas, the class OrdInt doesn't implement Ord. Mini-exercise: why not?
So let's try again.
public boolean less(Ord o) {return i < o.intValue();}Alas and alack, this doesn't do what we want either. (Why not?)
A correct version uses an F-bound (a type variable that appears both as the bounded variable and in the bound).
F-bounded polymorphism was introduced in the paper "F-bounded polymorphism for object-oriented programming." (A pdf file of the paper linked from the main 341 pizza page, but you aren't expected to read it unless you're interested.) That paper used a very simple model of object-oriented programming. Objects are records (perhaps with both simple and functional types as components) -- no classes, no information hiding, no implementation inheritance. The paper goes on to show how to correctly give types for binary operations, using F-bounds, and shows that the F-bound is the most general possible type bound that gives the correct semantics.
The Pizza version of F-bounded polymorphism follows. Note that in the definition of Pair (taken from the paper), elem appears both as the bounded variable and in the bound.
interface Ord<elem> { boolean less(elem o); } class OrdInt implements Ord<OrdInt> { int i; public boolean less(OrdInt o) {return i < o.intValue();} } class OrdString implements Ord<OrdString> { String s; public boolean less(OrdString o) {return s.stringCompare(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;} } Pair<OrdInt> p = new Pair (new OrdInt(41), new OrdInt(42)); System.out.println(p.min().intValue());Here's another example. Let's define an interface Translateable for objects that implement the translate method. Points, rectangles, and other graphical objects will all be translateable. translate should take an x and a y and return a new object of the same type, translated by the desired amount. So translating a point should return a point, translating a rectangle should return a rectangle, and so forth.
So this doesn't work:
interface Translateable { Translateable translate(int dx, int dy); }Instead, here's a correct interface and a class that implements it:
interface Translateable<elem> { elem translate(int dx, int dy); } class Point implements Translateable<Point> { public int x; public int y; Point(int x, int y) { this.x = x; this.y = y; } Point translate(int dx, int dy) { return new Point(x+dx,y+dy); } } class Rectangle implements Translateable<Rectangle> { ... } Point p1,p2; Rectangle r1,r2; p1 = new Point(100,200); p2 = p1.translate(1,1); r1 = new Rectangle(0,0,100,100); r2 = r1.translate(20,20);And another example, of things that have a min method:
interface Minimizable<elem> { elem min(elem x); } class Point implements Minimizable<Point> { public int x; public int y; Point min (Point p) { return new Point(Math.min(x,p.x),Math.min(y,p.y); } }