/* Doing Parametric types the right way. The new "generics" feature of C# 2.0 and Java jdk 1.5 both originated from the experimental languages "Pizza" (no kidding) and "GJ". Both of these languages derive from the research-oriented language ML, which is like Scheme except it has types. ML's type system is based on the "principal type schemes" of Hindley-Milner, who generalized the Typed Lambda Calculus. */ using System; // a parmametric delegate for functions that "subtracts one" public delegate T subtractfun(T x); // parametric cell class for linked lists public class cell { public T head; public cell tail; public cell(T h, cell t) { head=h; tail=t; } // rest assured that only one of these will be generated: public static int length(cell L) { int ax = 0; for(; L!=null; L=L.tail) ax++; return ax; } public void subone(subtractfun subone) // accepts function as param { for (cell i=this; i!=null; i=i.tail) { i.head = subone(i.head); } } /* Try uncommenting the following and see if it will compile, the C++ equivalent will! public static void suboneb(cell L) { for (cell i = L; i!=null; i=i.tail) { i.head = i.head - 1; } } *****/ } //cell public class genericsl { // functions to instantiate delegates: public static int deci(int x) { return x-1; } public static rational decr(rational r) { return new rational(r.n-r.d,r.d); } public static void Main() { cell A = new cell(2,new cell(4,new cell(6,null))); cell R = new cell(new rational(4,3), new cell(new rational(8,5),null)); cell S = new cell("abc",new cell("def",null)); A.subone( new subtractfun(deci) ); R.subone( new subtractfun(decr) ); Console.WriteLine(R.head.n); // just to see if it works (prints 1) // try this: it won't compile: // S.subone( new subtractfun(deci)); }//main }//genericsl //// class for rational numbers: public class rational { public int n, d; // numerator and denominator public rational(int a, int b) {n=a; d=b;} } /* Another way to write this program, with same level of polymorphism, is with interface substractable { void subone(); } class cell< T > // in Java, these 2 lines will read as: where T : subtractable // class cell> { .... public void subtractone() { for (cell i = this; i!=null; i=i.tail) { i.subone(); } } } class rational : subtractable { void subone() { ... } } This is the approach you'll probably take with Java, since Java doesn't have delegates (but you can simulate them by passing objects). Some people will regard this approach as more "object-oriented" compared to the delegates approach, which is more "functionally oriented". I believe that such categorizations are meaningless. The problem with this approach is that even for simple types like int and double you would have to wrap them inside a class that implements the subtractable interface (i.e., use object adapters). This is in my opinion less elegant than the delegate approach. It is highly unlikely when writing a class as a self-contained component, that you'll be able to anticipate all the interfaces that it'll have to implement in order to satisfy all the algorithms that may have to be written for it. The "more oop" approach will result in the use of myrid object adapters, resulting in rather cumbersome and messy code. ////////////////// MOST IMPORTANTLY ... //////////////////// Another variation is to simply declare the type of the "head" to be object: class ocell { public object head; public ocell tail; } How is this approach different from the generics approach? Consider: ocell A = new ocell("abc",new ocell("def",null)); cell G = new cell("abc",new cell("def",null)); A.head = (int)A.head - 1; // compiles, causes runtime exception HOWEVER: G.head = G.head - 1 // or even G.head = (int)G.head - 1 will cause a COMPILE TIME ERROR. Because the compiler knows that the type of G.head is string, not int. Using "object" is not the same as using type variables. The object form is useful if you must have lists containing data of different types (a list with both strings and ints for example). However, often we only need lists of ints and lists of strings. In such situations, using the supertype "objects" differs type-checking until runtime. This is better than not catching the type error at all, but clearly not as good as catching the error at compile time. The mechanism of parametric types or generics is orthogonal to oop; it can be applied to non-oop languages as well. But it can be shown, with some examples such as these, that it can be used to complement oop in a significant way. */