[a] -> [a] -> [a](the type of the append function).
You can't do this in Java, which 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.
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 Pair<A, B> { public A first; public B second; public Pair(A first, B second) { this.first = first; this.second = second; } }
Pair<String, int> getValues() { return new Pair(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? Answer this question first assuming Java's subtyping rules (equivariance with overloading) , and then assuming the contravariant rule.
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?)
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", linked from the optional reading list. 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 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);} }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); } }
double :: Integer -> Integer double x = 2*xand then use it to double every element in a list:
map double [1,2,3,4,5]Or we could use an anonymous function:
-- double every element of a list map (\x -> 2*x) [1,2,3,4,5]These versions of the member function illustrates the use of non-local variables in a function:
my_member a x = or (map (==a) x) -- or another (clunkier) version, with a lambda: my_member a x = or (map (\b -> a==b) x)
class Radix { int n = 0; (char)->boolean radix(int r) { return fun (char c)->boolean { n++; return '0' <= c && c < '0'+r; }; } String test () { (char)->boolean f = radix(8); return f('0') + " " + f('9') + " " + n; } }In the body of the abstraction (the function), c is a formal parameter, r is a free variable, and n is an instance variable.
Calling new Radix().test() returns:
"true false 2"
In more recent versions of Java, we can do much the same thing using inner classes:
class Radix { int n = 0; public class TestChar { int r; TestChar(r) { this.r = r; } public boolean test(char c) { n++; return '0' <= c && c < '0'+r; } } String test () { TestChar f = new TestChar(8); return f.test('0') + " " + f.test('9') + " " + n; } }Calling new Radix().test() should return the same thing.