/* pizza program with parametric types. */ /* To run this program on Hofstra Sun workstations, put the following in your .cshrc: alias pizzac java -jar /shared/csc/local/pizza-1.1.jar alias pizza java -cp ./:/shared/csc/local/pizza-1.1.jar To compile a program: pizzac sourcecode.pizza To run a program: pizza classname You can configure your own machine to run pizza similarly. */ interface team { void win(); void lose(); String name(); double percentage(); } class basicteam implements team { protected int wins = 0; protected int losses = 0; protected String name; public String name() { return name; } public void win() { wins++; } public void lose() { losses++; } public double percentage() { if (wins+losses==0) return 0; else return ((double)wins)/(wins+losses); } public basicteam(String n) { name = n; } } class footballteam extends basicteam implements team { private String quarterback; public String getqb() { return quarterback; } public footballteam(String n, String qb) { super(n); quarterback=qb; } // note different syntax from C# } class hockeyteam extends basicteam implements team { private int ties = 0; // hockey teams can have ties private String goalie; public void tie() { ties++; }; public String getgoalie() {return goalie;} public double percentage() // Java/Pizza always overrides { int games = wins+losses+ties; if (games<1) return 0; else return ((double)(wins+ties))/games; } public hockeyteam(String n,String gl) { super(n); goalie = gl; } } /////////// bounded polymorphic lists of sports teams: interface league { T get(String teamname); } class nil implements league { public T get(String s) { return null; } } class cell implements league { private T head; private league tail; public cell(T h, league tl) { head=h; tail=tl; } // and here is the key polymorphic method: public T get(String teamname) // return pointer to team { if (teamname.equals(head.name())) return head; // don't use == else return tail.get(teamname); } } /////////////////// compare to version without parameterized types: interface tleague { team get(String name); } class tnil implements tleague { public team get(String name) { return null; } } class tcell implements tleague { private team head; private tleague tail; public tcell(team h, tleague tl) { head=h; tail=tl; } public team get(String name) { if (name.equals(head.name())) return head; else return tail.get(name); } } ///// testing public class plain { public static void main(String[] args) { league NFL = new cell(new footballteam("jets","vinny"), new cell(new footballteam("giants","whoever"),new nil())); league NHL = new cell(new hockeyteam("rangers","good goalie"), new cell(new hockeyteam("islanders","bad goalie"),new nil())); NFL.get("jets").lose(); NFL.get("giants").win(); NHL.get("rangers").tie(); NHL.get("rangers").lose(); NHL.get("islanders").win(); // the following will cause a COMPILE TIME error: // NFL.get("giants").tie(); System.out.println(NHL.get("rangers").percentage()); System.out.println("------------- no generics version: ----------"); tleague Canada = // Canadian teams are not parametric new tcell(new hockeyteam("vancouver","great goalie"), new tcell(new hockeyteam("toronto","worst goalie"),new tnil())); // why is type casting below necessary? String gl = ((hockeyteam)Canada.get("toronto")).getgoalie(); // if the above is not bad enough, consider the following: String qb = ((footballteam)Canada.get("toronto")).getqb(); // This error is NOT caught at compile time. } // main } /* Both methods of polymorphism have advantages: with parameterized types, we cannot create a list of mixed footballteams and hockeyteams. But with the non-parametric method, type casting is more often needed, since the compiler won't know the actual types of objects. Type casting is also less efficient as the virtual machine must check the type tags of structures during runtime. In the case of boxing/unboxing (supported in Pizza and C# but not in Java), type casting can be quite expensive. But the most significant advantage of the parametric approach is the ability to type-check polymorphic code STATICALLY. It is an axiom of computer science to always do as much as possible at compile time so that running the program becomes both more efficient and safer from errors. Not that the parametric approach doesn't have its limitations. For example, the following is not legal: class nil implements league The type variable T is not bound - it must appear as a type parameter for nil as well, even though there's really no good reason for it to do so. Not every type expression with variables is legal. For example, you cannot have a function of type int->A or A->B. These are not propositional tautologies. If we want the ability to check or infer the consistency of types, then the forms of types must have logical structure. A function can have generic type A->A (lambda x.x) but no function can have generic type A->B (try to write one). Overall, parameterized types give an advantage in situations such as above. However, we still need inheritance polymorphism and dynamic dispatch. One of the articles on the web page, about parametric polymorphism in Java, tries to use the technique in conjunction with the Visitor pattern. But it doesn't work (the author was just a grad student, so don't be too hard on him). The problem is that the "accept" method should have different parameter and return types. We cannot write interface visitee { B accept(visitor v); } because B is not bound (we don't know how to instantiate it). We also cannot write interface visitee since then we have to determine the type of all possible visitors when the data object is created. The only way to do it efficiently that I can see is to have interface visitee { Object accept(visitor v); } and use dynamic dispatch as before. (there's also a very messy way using "object adaptors"). Fortunately, pizza demonstrates another way for having the style of programming one gets from the visitor pattern. */