1 / 48

Introduction to Pizza : an Extension to Java

Introduction to Pizza : an Extension to Java. Chegn-Chia chen. The Pizza Language. An extension to Java with three new features: Generics (aka Parametric polymorphism) Function pointers (aka First-class functions) Class cases and pattern matching (aka Algebraic types). outlines.

selma-russo
Télécharger la présentation

Introduction to Pizza : an Extension to Java

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Introduction to Pizza : an Extension to Java Chegn-Chia chen

  2. The Pizza Language • An extension to Java with three new features: • Generics (aka Parametric polymorphism) • Function pointers (aka First-class functions) • Class cases and pattern matching (aka Algebraic types)

  3. outlines • Parametric Polymorphism (Generics or Templates) including • Bounded Polymorphism • F-bounded Polymorphism • First-class functions (Function pointers or Function variables) • Anonymous functions • Algebraic types including • Pattern matching • Enumeration types (enum) • Type casts • Tail call recursion • Pizza's API classes

  4. Parametric Polymorphism • allow us to write classes that operate on data without specifying the data's type. • Ex: class Store<A> { A data; Store(A data) { this.data = data; } void set(A data) { this.data = data; } A get() { return data; } } • is a general classes with type parameter <A>

  5. Instantiation of parametric classes Store<String> s1 = new Store(“This is a string!"); Store<int> s2 = new Store(23 +14); s2.set(19); int i = s2.get(); String s = s1.get(); • Notes: • when allocating an Object of a parametric type, we don't need to pass the instance type. (i.e., not new Store<int>(23 + 14) ) • The Pizza compiler infers this type from the constructor arguments

  6. What we would use with Java • Strategy 1: replace all type parameter with Object. class Store /*<A>*/ { Object /*A*/ data; Store(Object /*A*/ data) { this.data = data; } void set((Object /*A*/ data) { this.data = data; } Object /*A*/ get() { return data; } } … Store s1 = new Store(“This is a string!”);s1.set(“new string”); String rlt = (String) s1.get(); • Problems: • 1. can not deal with int case. • 2. additional type cast required.

  7. Extend parametric Class class StoreString extends Store<String> { Store(String something) { super(something); } } • special class storing only String object. class Store2<A> extends Store<A> { Store2(A data) { super(data); } void print() { System.out.println(""+data); } } • This gives us a parameterised class extending the other parameterised class.

  8. The Pair Parametric class • public class Pair<A, B> { public A fst; public B snd; public Pair(A fst, B snd) { this.fst = fst; this.snd = snd; } } • This class becomes very handy, if you need a method to return two values: • Pair<String, int> getValues() { return new Pair(aString, anInt); } • included in the standard Pizza classes class pizza.Pair

  9. Parametric Interfaces • All the things we did to classes can also be done to interfaces. • Ex: sets of elements of a parameterised type A: interface Set<A> { boolean contains(A x); Set<A> include(A x); // return this U {x}. } • The method include(A) shows us the sets defined by this interface are persistent: • Every time we include an element we get a new set (it doesn't change the old one).

  10. Implement Set<A> using LinkedList • abstract class LinkedList<A> implements Set<A> { public abstract boolean contains(A x); public LinkedList<A>insert(A x) { if (contains(x)) return this; else return new NonEmptyList(x, this); } public Set<A>include(A x) { return insert(x); } } • class EmptyList<A> extends LinkedList<A> { public boolean contains(A x) { return false; } }

  11. class NonEmptyList<A> extends LinkedList<A> { private A head; private LinkedList<A> rest; public NonEmptyList(A elem, LinkedList<A> rest) { this.head = elem; this.rest = rest; } public boolean contains(A x) { return ((Object)head).equals((Object)x) || rest.contains(x); } }

  12. Example • This program tests whether its first command line argument is repeated in subsequent arguments. • public class TestForDuplicates { public static void main(String[] args) { Set<String> set = new EmptyList(); for (int i = 1; i < args.length; i++) set = set.include(args[i]); System.out.println(set.contains(args[0])); } }

  13. Bounded Polymorphism • Implement sets using a binary tree. • need know how to compare the elements. • have to set up a constraint on our parameter type - bind the type. • Set up an interface to which we'll bind our parameter types: • interface Comparable { boolean equals(Object x); boolean less(Object x); } • interface Set<A implements Comparable> { boolean contains(A x); Set<A> include(A x); } • This means we can only use types that implement Comparable as parameters to the interface Set.

  14. Implement Set using Tree • abstract class Tree<A implements Comparable> implements Set<A> { public abstract boolean contains(A x); public abstract Tree<A> insert (A x); public Set<A> include(A x) { return insert(x); } } • class EmptyTree<A implements Comparable> extends Tree<A> { public boolean contains(A x) { return false; } public Tree<A> insert(A x) { return new Branch(x, this, this); } }

  15. class Branch<A implements Comparable> extends Tree<A> { A elem; Tree<A> left; Tree<A> right; public Branch(A elem, Tree<A> left, Tree<A> right) { this.elem = elem; this.left = left; this.right = right; } public boolean contains(A x) { if (x.less(elem)) return left.contains(x); else if (elem.less(x)) return right.contains(x); else // we assume total ordering, therefore x.equals(elem) return true; }

  16. public Tree<A> insert(A x) { if (x.less(elem)) return new Branch(elem, left.insert(x), right); else if (elem.less(x)) return new Branch(elem, left, right.insert(x)); else // we assume total ordering, therefore x.equals(elem) return this; } }

  17. Use Case • class ComparableString implements Comparable { private String s; ComparableString(String s) { this.s = s; } public boolean less(Object other) { if (other instanceof ComparableString) return s.compareTo( ((ComparableString)other).s) < 0; else throw new Error("can't compare to non-strings"); } } • note: Some type casts are still needed!

  18. Use Case (continued) • public class TestForDuplicates { public static void main(String[] args) { Set<ComparableString> set = new EmptyTree(); for (int i = 1; i < args.length; i++) set.include(new ComparableString(args[i])); System.out.println( set.contains(new ComparableString(args[0]))); } }

  19. F-bounded Polymorphism • Parameterize Comparable interface • interface Comparable<A> { boolean less(A x); } • interface Set<A implements Comparable<A>> { boolean contains(A x); Set<A> include(A x); } • Now ComparableString can be defined as follows: • class ComparableString implements Comparable<ComparableString> { private String s; ComparableString(String s) { this.s = s; } public boolean less(ComparableString other) { return s.compareTo(other.s) < 0; }}

  20. Polymorphic Functions • Type parameter cannot appear at static methods: • abstract class Tree<A implements Comparable> implements Set<A> { public abstract boolean contains(A x); public abstract Tree<A> insert (A x); public Set<A> include(A x) { return insert(x); } public static Tree<A> makeTree(A e) {…} // illegal ! } • (0) Tree<String> t = new Tree(…); t.insert(s1) … • (x) Tree<Integer> t2 = Tree.makeTree( new Integer(s))…

  21. Sometimes, we still need to have polymorphic static functions. Or, need a dynamic method which is polymorphic independently of any type parameters. • This can be done by prefixing the method return type with a type-variable section: • class TreeUtils { static <A implements Comparable<A>> Tree<A> insertArray(Tree<A> tree, A[] elems) { for (int i = 0; i < elems.length; i++) tree = tree.insert(elems[i]); return tree; } }

  22. Implementing parametric types • Two translations are possible • The homogeneous translation map type variables to Objects and inserts appropriate casts. • The heterogeneoustranslation creates many instances of a parameterised class, one for each instance type. • Pizza is designed so that any of the two translations can be used. • explained in more detail in the language definition and the Paper "Two ways to bake your Pizza". • The homogeneous translation effectively erases type information. • This leads to a couple of restrictions on the uses of parameterised types. • In the current Pizza compiler the homogeneous translation is used only!

  23. Restrictions on parametric types • The target type of a coercion must be a Java type -- no type parameters allowed • OK: (Tree)object which is of type Tree<A>, for some unknown type variable A. • Not OK: (Tree<int>)object • The element type of an array creation expression new T[...] must also be a Java type. • OK: new Tree[10] This allocates an array of 10 trees. Their element type is determined by the context. • Not OK: new A[10] where A is a type variable.

  24. First class functions • Use functions as special kinds of data types. • can store a function in a variable, • can access it and • can pass a function as parameter to method without any knowledge about the class it's defined in. • like function pointers in C++. • Can avoid the effort of wrapping classes to make them implement a special interface. • We define all the functions we need to be passed as parameters to our methods. • we pass a function as parameter to a function!)

  25. The syntax • The syntax for function types is:(argtype, ..., argtype) -> resulttype. • Or, if exceptions are thrown by the function:(argtype, ..., argtype) throws exception, ..., exception  -> resulttype. • Any method with a matching signature may be substituted for a function parameter. • Ex: • boolean doIt(int, String) matches • (int, String) -> boolean • int read(InputStream) throws IOException matches • (InputStream) throws IOException -> int.

  26. Example class PrintSortedStrings { static void print( String[] args, (String, String) -> int compare) { .... if (compare(args[i], args[j]) > 0) .... } } • cf: • interface Comparator { int compare(String,String); } • class PrintSortedStrings { static void print( String[] args, Comparator comparator) { .... if (comparator.compare(args[i], args[j]) > 0).... } }

  27. To uses the compareTo method of the class String in one case and a case insensitive version in another we can now write: • int myCompare(String s1, String s2){ return s1.compareTo(s2); } • int myCompareNoCase(String s1, String s2){ return s1.toLowerCase().compareTo(s2.toLowerCase()); } • String[] myStrings = { "These", "Strings", "will", "be", "sorted", "!"}; • PrintSortedStrings.print(myStrings,myCompare); • PrintSortedStrings.print(myStrings,myCompareNoCase);

  28. First class function in parametric Types • The use of first class functions becomes much more important if you are using parametric polymorphism. • In the tree example: • needed the method less introduced via an interface. • Refine the class Tree to take a function parameter to the methods contains and insert: • abstract class Tree<A> { public abstract boolean contains( (A,A)->boolean less, A x); public abstract Tree<A> insert( (A,A)->boolean less, A x); } • Note: less is now a function object instead of a Comparable<A> Object.

  29. These methods can now use the variable less to use it's function, as if it was defined locally: public boolean contains((A, A) -> booleanless, A x) { if (less(x, elem)) return left.contains(less, x); else if (less(elem, x)) return right.contains(less, x); else // we assume total ordering, therefore x.equals(elem) return true; }

  30. Reimplement Branch<A> • Not: It is no longer needed to restrict <A> to implement Comparable<A>. • , which was not possible in the F-bounded version via the interface. • class Branch<A> extends Tree<A> { A elem; Tree<A> left; Tree<A> right; public Branch(A elem, Tree<A> left, Tree<A> right) { this.elem = elem; this.left = left; this.right = right; }

  31. public boolean contains((A, A) -> boolean less, A x) { if (less(x, elem)) return left.contains(less, x); else if (less(elem, x)) return right.contains(less, x); else // we assume total ordering, therefore x.equals(elem) return true; } public Tree<A> insert((A, A) -> boolean less, A x) { if (less(x, elem)) return new Branch(elem, left.insert(less, x), right); else if (less(elem, x)) return new Branch(elem, left, right.insert(less, x)); else return this; }}

  32. Tree<String> • Extend the Branch class and overload the methods contains and insert: class StringBranch extends Branch<String> { StringBranch(String elem, Tree<String> left, Tree<String> right) { super(elem, left, right); } private static boolean stringLess(String x, String y) { return x.compareTo(y) > 0; } public boolean contains(String x) { return contains(stringLess, x); } public Tree<String> insert(String x) { return insert(stringLess, x); }}

  33. Anonymous Functions • Instead of defining a method belonging to a class Pizza allows you to define functions locally. • These functions don't have a name that's why they are known as anonymous functions. • Pizza's syntax to define an anonymous function is: fun(arguments)-> resulttype { statements }

  34. Example: anonymous function public class TestForDuplicatesAnonymous { public static void main(String[] args) { Set<String> s = new TreeSet( fun(String x, String y) -> boolean { return x.compareTo(y) < 0; } ); for (int i = 1; i < args.length; i++) s.include(args[i]); System.out.println(s.contains(args[0])); } }

  35. Example • () -> int makeIncr(int start) { int count = start; return fun() -> int { return count++; } } • () -> int incr1 = makeIncr(0); • () -> int incr2 = makeIncr(5); • System.out.println( incr1() + " " + incr1() + " " + incr2() + " " + incr1()); • will return 0 1 5 2.

  36. Traverse a tree • main benefit of first class function. // can replace visitor • void <A> traverse(Tree<A> t, (A)->void proc) { if (t instanceof Branch) { Branch b = (Branch) t; traverse(b.left, proc); proc(b.elem); traverse(b.right, proc); }} • We can use this method to sum up all elements of a tree of integers: • int sum(Tree<int> t) { int n = 0; traverse(t, fun(int x) -> void { n += x; }); return n; }

  37. Algebraic Data Types Shape  Circle, Rectangle, Triangle,… void draw(Shape s) { if(s instanceof Circle) { Circle c = (Circle) s; …} else if (s instanceof Rectangle) { Rectangle r = (Rectangle) s; …} else … • Problem: • 1. many type checks and type casts required. • 2. One abstract class many concrete subclasses.

  38. Algebraic data types • Pizza gives us an easy to use syntax to avoid the complicated abstract class/concrete cases construction: • In Pizza class can contain declarations of these forms: • case ident(arguments); • case ident; • With this syntax our Tree class becomes: class Tree<A> implements Set<A> { case Empty; case Branch(Aelem, Tree<A>left, Tree<A>right); }

  39. This example defines one class Tree which • provides the variable Empty representing empty branches and • a method Branch(A, Tree<A>, Tree<A>) instantiating a representation for a non-empty branch. • public class Test { public static void main(String[] args) { Tree<int> t = Branch(2, Branch(1, Empty, Empty), Branch(3, Empty, Empty)); } }

  40. Pattern matching • Pizza internally generates inner classes to represent algebraic cases of a class. • In these classes the arguments we provide in the case syntax become variables. • We could access the element stored in a branch this way: Tree<String> t = ... ; String s; if (t instanceof Branch) { s = (Branch) t.elem; }else { s = "(empty)"; }

  41. Access instances of an algebraic class Pizza provides a very simple way to access instances of an algebraic class similar to Java's switch statement class Tree<A> { case Empty; case Branch(A elem, Tree<A> left, Tree<A> right); public boolean contains((A, A) -> boolean less, A x) { switch (this) { // not legal in java since this is not integral case Empty: return false; case Branch(A elem, Tree<A> l, Tree<A> r): if (less(x, elem)) return l.contains(less, x); else if (less(elem, x)) return r.contains(less, x); else return true; } }

  42. Pattern matching public Tree<A> insert( (A, A) -> boolean less, A x) { switch (this) { case Empty: return Branch(x, Empty, Empty); case Branch(A e, Tree<A> l, Tree<A> r): if (less(x, e)) return Branch(e, l.insert(less, x), r); else if (less(e, x)) return Branch(e, l, r.insert(less, x)); else return this; } }} • switch (var) does two things for us: • 1. select the case corresponding to var. • 2. bind all formal variables with the corresponding fields (l  this.left ; r  this.right; e  this.elem).

  43. Don’t Care fields in the pattern • We can replace parameters we don't want to access with a single '_': Tree<A> goRight() { switch (this) { case Empty: return this; case Branch(_, _, Tree<A> r): return r.goRight(); } }

  44. Deep pattern matching • Pattern matching is not restricted to just one level of variables. • static <A,B> List<C> mapPairs((A,B)->C f, List<A> xs, List<B> ys) { List<C> result; switch (Pair.Pair(xs, ys)) { case Pair(List.Nil, _): case Pair(_, List.Nil): result = List.Nil; break; case Pair( List.Cons(A x, List<A> xs1), List.Cons(B y, List<B> ys1) ): result = List.Cons( f(x, y), mapPairs(f, xs1, ys1)); break; case _: System.out.println("case 2"); break; } return result; }

  45. Special case: Classes with a single constructor • The algebraic class syntax is so convenient that it's useful even if there are no sub-cases. • An example is the class Pair: class Pair<A,B> { case Pair(A fst, B snd);} is a shorthand for class Pair<A,B> { A fst; B snd; Pair(A fst, B snd) { this.fst = fst; this.snd = snd; } } • If there's only one constructor, no separate class for the case will be produced. Therefore, it's OK to use the same name Pair for the class and the case.

  46. Special case: Enums By defining a class with only constant subcases we get enumeration types, which are missing in Java (compared to C): class Color { case Red; case Green; case Blue; String toString() { switch (this) { case Red: return "Red"; case Green: return "Green"; case Blue: return "Blue"; } }} • This is often preferable to using sets of integer constants since a typecheck by the compiler is provided and you can't use an undefined value.

  47. Type casts in Pizza • To use parametric polymorphism even for Java's basic types we sometimes need the ability to cast between objects and basic types. • Pizza provides typecasts from basic types to their boxing classes and back: int basicX; Integer integerX; Object objectX; basicX = (int)integerX; basicX = (int)objectX; integerX = (Integer)basicX; objectX = (Object)basicX; • However, as always typecasts can go wrong during runtime!

  48. Tail Call Recursion continueint veryRecursive(int howDeep) { if (howDeep == 0) { System.out.println("I'm there -- finally!"); return -1; } else returngoto veryRecursive(howDeep - 1); }

More Related