parametric polymorphism antonio cisternino. parametric polymorphism c++ templates implement a form...
Post on 19-Dec-2015
224 views
TRANSCRIPT
Parametric Polymorphism
C++ templates implement a form of parametric polymorphism
PP is implemented in many flavors and many languages: Eiffel, Mercury, Haskell, ADA, ML, C++…
Improve the expressivity of a language May improve the performance of
programs It is a form of Universal polymorphism
C++ templates and macros
Macros are dealt by the preprocessor C++ templates are implemented on the syntax tree The instantiation strategy is lazy The following class compiles unless the method foo
is used:template <class T>class Foo { T x; int foo() { return x + 2; }};
Foo<char*> f;f.x = “”;f.foo();
A more semantic approach
Parametric polymorphism has been introduced also in Java and C# Java Generics and Generic C# for .NET
In both cases the compiler is able to check parametric classes just looking at their definition
Parametric types are more than macros on AST
Syntax for generics is similar in both JG and C#
Generics in a Nutshell
Type parameterization for classes, interfaces, and methods e.g.
class Set<T> { ... } // parameterized classclass Dict<K,D> { ... } // two-parameter classinterface IComparable<T> { ... } // parameterized interfacestruct Pair<A,B> { ... } // parameterized struct (“value class”) T[] Slice<T>(T[] arr, int start, int count) // generic method
Very few restrictions on usage: Type instantiations can be primitive (only C#) or class e.g.
Set<int> Dict<string,List<float>> Pair<DateTime, MyClass> Generic methods of all kinds (static, instance, virtual) Inheritance through instantiated types e.g.
class Set<T> : IEnumerable<T>class FastIntSet : Set<int> Virtual methods
only in GC#!
In GJ is<T> T[] Slice(…)
C#
JG
C++
Generic methods are similar to template methods in C++ As in C++ JG tries to infer the type parameters from the
method invocation C# requires specifying the type arguments Example:
template <class T> T sqr(T x) { return x*x; }std::cout << sqr(2.0) << std::endl;class F { <T> static void sort(T[] a) {…} }String[] s; F.sort(s);class F { static void sort<T>(T[] a) {…} }string[] s; F.sort<string>(s);
More on generic methods
Generic Stack
class Stack<T> { private T[] items; private int nitems; Stack<T> { nitems = 0; items = new T[] (50); } T Pop() { if (nitems == 0) throw Empty(); return items[--nitems]; } bool IsEmpty() { return (nitems == 0); } void Push(T item){ if (items.Length == nitems) { T[] temp = items; items = new T[nitems*2]; Array.Copy(temp, items, nitems); }
items[nitems++] = item;}
}
How does the compiler
check the definition?
Tip C++ requires a space in nested parameter types: vector<vector<int> > to avoid
ambiguity with operator >> GJ (and C#) fixed the problem with the following grammar:
ReferenceType ::= ClassOrInterfaceType | ArrayType | TypeVariableClassOrInterfaceType ::= Name | Name < ReferenceTypeList1ReferenceTypeList1 ::= ReferenceType1 | ReferenceTypeList , ReferenceType1ReferenceType1 ::= ReferenceType > | Name < ReferenceTypeList2ReferenceTypeList2 ::= ReferenceType2 | ReferenceTypeList , ReferenceType2ReferenceType2 ::= ReferenceType >> | Name < ReferenceTypeList3ReferenceTypeList3 ::= ReferenceType3 | ReferenceTypeList , ReferenceType3ReferenceType3 ::= ReferenceType >>>TypeParameters ::= < TypeParameterList1TypeParameterList1 ::= TypeParameter1 | TypeParameterList , TypeParameter1TypeParameter1 ::= TypeParameter > | TypeVariable extends ReferenceType2 |
TypeVariable implements ReferenceType2
The semantic problem
The C++ compiler cannot make assumptions about type parameters
The only way to type-check a C++ class is to wait for argument specification (instantiation): only then it is possible to check operations used (i.e. comp method in sorting)
From the standpoint of the C++ compiler semantic module all types are not parametric
Checking class definition
To be able to type-check a parametric class just looking at its definition we introduce the notion of bound
As in method arguments have a type, type arguments are bound to other types
The compiler will allow to use values of such types as if upcasted to the bound
Example: class Vector<T : Sortable> Elements of the vector should implement (or
inherit from) Sortable
Example
interface Sortable<T> { int compareTo(T a);}class Vector<T : Sortable<T>> { T[] v; int sz; Vector() { sz = 0; v = new T[15]; } void addElement(T e) {…} void sort() { … if (v[i].compareTo(v[j]) > 0) … }}
Compiler can type-check this because v contains values that
implement Sortable<T>
Not possible in Java, because Sortable is an
interface and type T is lost.
Pros and Cons
A parameterized type is checked also if no instantiation is present
Assumptions on type parameters are always explicit (if no bound is specified Object is assumed)
Is it possible to made assumptions beyond bound? Yes, you can always cheat by upcasting to Object and then to
whatever you want:class Foo<T : Button> { void foo(T b) { String s = (String)(Object)b; }}
Still the assumption made by the programmer is explicit
Implementation
Alternative implementations of parametric polymorphism:
C++ generates Abstract Syntax Tree for method and classes
GJ implements generic types at compile time: the JVM is not aware of parametric types
C# assumes that CLR is aware of parametric types: the IL has been extended with generic instructions to handle with type parameters
Java Generics strategy
JG is an extension of Java The compiler verifies that generic types
are used correctly Type parameters are dropped and the
bound is used instead; downcasts are inserted in the right places
The output is a normal class file unaware of parametric polymorphism
Example
class Vector<T> { T[] v; int sz; Vector() { v = new T[15]; sz = 0; } <U implements Comparer<T>> void sort(U c) { … c.compare(v[i], v[j]); … }}…Vector<Button> v;v.addElement(new Button());Button b = v.elementAt(0);
class Vector { Object[] v; int sz; Vector() { v = new Object[15]; sz = 0; } void sort(Comparer c) { … c.compare(v[i], v[j]); … }}…Vector v;v.addElement(new Button());Button b =
(Button)b.elementAt(0);
Wildcard
class Pair<X,Y> { X first; Y second;
}public String pairString(Pair<?, ?> p) {
return p.first + “, “ + p.second;}
Expressivity vs. efficiency
JG doesn’t improve execution speed; though it helps to express genericity better than inheritance
There is a main limit in JG expressivity: at runtime exact type information is lost
All instantiations of a generic type collapse to the same class
Consequences are no virtual generic methods and pathological situations
Benefit: Java classes could be seen as generic types! Reuse of the large existing codebase
JG isn’t the only implementation of generics for Java
Generics and JavaJG/PizzaBracha, Odersky,Stoutamire, Wadler
NextGen Cartwright, Steele
PolyJ Bank, Liskov, Myers
Agesen, Freund, Mitchell
Generic CLRKennedy,Syme
Parameterized types
+ bounds
+ bounds
+
constraints
+ bounds
+ bounds
Polymorphic methods Type checking at point of definition Non-reference instantiations Exact run-time types ? Polymorphic virtual methods Type parameter variance
System
Feature
Problem with JG
Stack<String> s = new Stack<String>();
s.push("Hello");
Stack<Object> o = s;
Stack<Button> b = (Stack<Button>)o;
// Class cast exception
Button mb = b.pop(); Cast authorized: both Stack<String> and Stack<Button> map to class Stack
Generic C# Strategy: GCLR
Kennedy and Syme have extended CLR to support parametric types (the same proposal has been made for PolyJ by Cartwright and Steele)
In IL placeholders are used to indicate type arguments (!0, !1, …)
The verifier, JIT and loader have been changed When the program needs an instantiation of a
generic type the loader generates the appropriate type
The JIT can share implementation of reference instantiations (Stack<String> has essentially the same code of Stack<Object>)
Generic C# compiler
GC# compiler implements a JG like notation for parametric types
Bounds are the same as in JG NO type-inference on generic methods: the
type must be specified in the call The compiler relies on GCLR to generate the
code Exact runtime types are granted by CLR so
virtual generic methods are allowed All type constructors can be parameterized:
struct, classes, interfaces and delegates.
Example
using System;namespace n { public class Foo<T> { T[] v; Foo() { v = new T[15]; } public static void Main(string[] args) { Foo<string> f = new Foo<string>(); f.v[0] = "Hello"; string h = f.v[0]; Console.Write(h); } }}
.field private !0[] v
.method private hidebysig specialname
rtspecialnameinstance void .ctor() cil
managed { .maxstack 2 ldarg.0 call instance void
[mscorlib]System.Object::.ctor()
ldarg.0 ldc.i4.s 15 newarr !0 stfld !0[] class n.Foo<!
0>::v ret} // end of method Foo::.ctor
Performance
The idea of extending CLR with generic types seems good; but how about performance?
Although the instantiation is performed at load time the overhead is minimal
Moreover code sharing reduces instantiations, improving execution speed
A technique based on dictionaries is employed to keep track of already instantiated types
Expressive power of Generics
System F is a typed -calculus with polymorphic types
While Turing-equivalence is a trivial property of programming languages; for a type-system being equivalent to System F it is not
Polymorphic languages such as ML and Haskell cannot fully express System F (both languages have been extended to fill the gap)
System F can be transposed into C# http://www.cs.kun.nl/~erikpoll/ftfjp/2002/KennedySyme.pdf
Reminder: substitutivity
Sub-Typing/Sub-Classing defines the class relation “B is a sub-type of A”, marked B <: A.
According to the substitution principle, if B <: A, then an instance of B can substitute an instance of A.
Therefore, it is legal to assign an instance b of B to a reference of AA a = b
Generics and Subtyping
Does the rules for sub-types and assignment works for generics?If B <: A, then G<B> <: G<A>?
List<String> ls = new List<String>();List<Object> lo = ls;// Since String <: Object, so far so good.lo.add(new Object());String s = ls.get(0); // Error!
The rule B <: A G<B> <: G<A> defies the principle of substitution!
Counter example
Other example
class B extends A { … }class G<E> { public E e;}G<B> gb = new G<B>();G<A> ga = gb;ga.e = new A();B b = gb.e; // Error!
Given B <: A, and assuming G<B> <: G<A>, then:G<A> ga = gb;would be legal.Actually, type is erased.
Bounded Wildcard
A wildcard does not allow doing much
To provide operations with wildcard types, one can specify bounds:
Upper Bound The ancestor of unknown:G<? extends X>
Lower Bound The descendant of unknown:G<? super Y>
Bounded Wildcards Subtyping Rules
For any B such that B <: A: G<B> <: G<? extends A> G<A> <: G<? super B>
Bounded Wildcards - Example
G<A> ga = new G<A>();
G<B> gb = new G<B>();
G<? extends A> gea = gb;
// Can read from
A a = gea.e;
G<? super B> gsb = ga;
// Can write to
gsb.e = new B();
G<B> <: G<? extends A>
hence legal
G<A> <: G<? super B>
hence legal
Generics and Polymorphism
class Shape { void draw() {…} }class Circle { void draw() {…} }class Rectangle { void draw() {…} }
public void drawAll(Collection<? extends Shape> shapes) { for (Shape s: shapes) s.draw();}
Collection<Shape> will not work. Why?
Lower Bound Example
interface sink<T> { flush(T t);}
public <T> T flushAll(Collection<T> col, Sink<T> sink)
{ T last; for (T t: col) { last = t; sink.flush(t);} return last;}
Lower Bound Example (2)
Sink<Object> s;
Collection<String> cs;
String str = flushAll(cs, s); // Error!
Lower Bound Example (3)
public <T> T flushAll(Collection<T> col, Sink<T> sink) { … }
…String str = flushAll(cs, s); // Error!
T is now solvable as Object, but it is not the correct type: should be String
Lower Bound Example (4)
public <T> T flushAll(Collection<T> col, Sink<? Super T> sink) { … }
…
String str = flushAll(cs, s); // OK!
Combining generics and Combining generics and inheritanceinheritance The inheritance relation must be extended
with a new subtyping rule:
Can now cast up and down to Object safely Note: types must be substituted because the
super-class can be parametric
Givenclass C<T1,...,Tn> extends B
we have C<t1,...,tn> <: B[t1/T1, ..., tn/Tn]
Manipulating types
Grouping values into types has helped us to build better compilers
Could we do the same with types? Types can be grouped by means of
inheritance which represents the union of type sets
Parametric types combined with inheritance allow expressing function on types:class Stack<T:object> : Container
Function name Function arguments Result type
Example: generic containers
class Row<T : Control> : Control{ /* row of graphic controls *> }class Column<T : Control> : Control{ /* column of graphic controls */ }class Table<T : Control> : Row<Column<T>>{ /* Table of graphic controls */ }…// It generates the keypad of a calculatorTable<Button> t = new Table<Button>(3, 3);for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) t[i, j].Text = (i * 3 + j + 1).ToString();