+ All Categories
Home > Documents > 1 Introduction to Pizza : an Extension to Java Chegn-Chia chen.

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

Date post: 19-Dec-2015
Category:
View: 228 times
Download: 2 times
Share this document with a friend
48
Introduction to Pizza : an Extension to Java Chegn-Chia chen
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 Classclass 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 heterogeneous translation 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

Exampleclass 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) -> 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; }

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 functionpublic 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 TypesShape 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(A elem, 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: EnumsBy 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 Recursioncontinue int veryRecursive(int howDeep) {

if (howDeep == 0) {

System.out.println("I'm there -- finally!");

return -1; }

else return goto veryRecursive(howDeep - 1);

}


Recommended