Generic Java Elena Camossi [email protected] Sommario GJ polimorfismo parametrico e tipi generici contesto storico caratteristiche principali Featherweight GJ: GJ il type system GJ estensione del linguaggio Java con supporto diretto di tipi e metodi generici (generics) GJ: polimorfismo parametrico approccio generale per descrivere dati e algoritmi la cui struttura è indipendente dal tipo degli elementi trattati Es: algoritmo che scambia due elementi dello stesso tipo class Pair<elem> { elem x; elem y; Pair(elem x, elem y) {this.x=x; this.y=y;} void swap() {elem t= x; x=y; y=t;}} ... Pair<String> p= new Pair(“world!” , ”Hello”); p.swap(); System.out.println(p.x+p.y); GJ: polimorfismo parametrico simulazione nel linguaggio tradizionale approccio omogeneo Es: class Pair { Object x; Object y; Pair(Object x, Object y) {this.x=x; this.y=y;} } void swap() {Object t= x; x=y; y=t;} Pair p= new Pair((Object)“world!” , (Object)”Hello”); p.swap(); System.out.println((String)p.x+(String)p.y); GJ: polimorfismo parametrico simulazione nel linguaggio tradizionale approccio eterogeneo (idea C++) Es: class Pair_String { String x; String y; Pair(String x, String y) {this.x=x; this.y=y;} } void swap() {String t= x; x=y; y=t;} Pair_String p= new Pair_String ( “world!” , ”Hello”); p.swap(); System.out.println(p.x+p.y); GJ: polimorfismo parametrico svantaggi della simulazione approccio omogeneo il codice diventa pesante da scrivere e leggere per l’introduzione dei cast approccio eterogeneo (idea C++) duplicazione del codice GJ: polimorfismo parametrico polimorfismo bounded il parametro di un tipo generico può essere una qualunque classe sottoclasse di una classe data (o una classe qualunque che implementa una data interfaccia) Es: class Pair<elem implements Ord>{...} GJ: polimorfismo parametrico polimorfismo F-bounded bound ricorsivi Es: interface Ord<elem> {...} class Pair<elem implements Ord<elem>>{...} GJ: storia presentato nel 1998 [BOSW98] (Pizza [OW97]) generici da sempre presenti in Java, ma senza supporto diretto ... ... così come in altri linguaggi oo (es. Oberon, Smalltalk) progetti equivalenti per altri linguaggi: Modula3, Ada95, Eiffel, C++, ... altre proposte per estensioni generiche per Java [AFM97] ,[CS98], [MBL97], [TT98] status: disponibile compilatore freeware per GJ [GJc] prossima l’integrazione di GJ nell’ sdk 1.5 [Java] GJ: caratteristiche gestione del codice legacy parametri scope e bound type inference semantica, safety e implementazione per traduzione nel linguaggio tradizionale GJ: codice legacy prbl1: codice Java legale se visto come codice GJ? ok da Pizza prbl2: come ottenere la compatibilità fra codice legacy e codice parametrico? possibili soluzioni (in Pizza) riscrivere il codice legacy scrivere degli adattatori che effettuino la conversione da legacy a parametrico (se il codice sorgente è disponibile) GJ: codice legacy GJ introduce i raw types, tipi parametrici privati dei parametri Es: Collection<A> Collection tipo parametrico raw type un tipo parametrico può essere utilizzato dove sia richiesto un tipo legacy retrofitting: meccanismo che utilizza informazioni aggiuntive sui tipi di campi e metodi, rese disponibili a run time, per eliminare gli warning la combinazione di retrofitting e regole di tipo per i raw types assicura l’assenza di errori a run time GJ: i parametri Es: interface Collection<A>{ public void add (A x); public Iterator<A> iterator();} interface Iterator<A>{ public A next (); public boolean hasNext();} class LinkedList<A> implements Collection<A>{ protected class Node { A elt; ... Node (A elt) {this.elt=elt;}} protected Node head =null, tail=null; public LinkedList(); public void add (A elt) { ...} public Iterator<A> iterator {...}...} class Test{ ... LinkedList<String> ys=new LinkedList<String>(); ys.add(“zero”); ys.add(“one”); String y = ys.iterator().next();} Scope del parametro la classe, comprese le classi interne, esclusi i membri e gli inizializzatori statici GJ: i parametri Es: Bound del parametro default: extends Object interface Comparable<A>{ public int compareTo (A that); } class Byte implements Comparable<Byte>{ private byte value; public int compareTo (Byte that){ return this.value-that.value;}...} class Collections { public static <A implements Comparable<A>> A max (Collection<A> xs){ Iterator<A> xi=xs.iterator(); ...}} Bound del parametro F-bounded GJ: type inference prbl: inferire il tipo di un parametro per un metodo generico Es: class Collections { public static <A implements Comparable<A>> A max (Collection<A> xs){ Iterator<A> xi=xs.iterator(); ...}} istanziazione del tipo dell’argomento nel momento in cui il metodo viene richiamato es: Collection<Byte> ys; Byte x = Collections.max(ys); A è di tipo Byte GJ: type inference algoritmo di GJ locale lavora per empty l’inferenza determina i tipi migliori, anche per valori che possono presentare molte alternative (es. lista vuota) supporta la regola di subsumption il tipo di un’espressione dipende solo dai tipi delle sottoespressioni, e non dal contesto in cui compare se un’espressione ha un certo tipo, può essere vista con tipo un suo qualunque supertipo regola generale: viene scelto il tipo più specializzato (se esiste) che porta ad una chiamata valida per il metodo GJ: traduzione semantica e implementazione di GJ date per traduzione in codice Java codice GJ corretto viene tradotto in codice Java corretto, che non produce errori a run time codice di GJ compilato in bytecode per la JVM => compatibilità GJ: traduzione compilatore per GJ scritto in GJ che effettua una traduzione omogenea, o per cancellazione 1. i parametri vengono cancellati 2. i tipi dei parametri vengono sostituiti con il bounding types (es. Object) 3. vengono aggiunti i cast necessari 4. vengono inseriti dei metodi bridge per il corretto funzionamento dell’overriding dei metodi GJ: esempio di traduzione 1 Es: interface Collection { public void add (Object x); public Iterator iterator();} class LinkedList implements Collection { protected class Node { Object elt; ... Node (Object elt) {this.elt=elt;}} protected Node head =null, tail=null; public LinkedList(); public void add (Object elt) { ...} public Iterator iterator {...}} interface Iterator { public Object next (); public boolean hasNext();} Cancellazione parametri Collection<A> Collection Iterator<A> Iterator LinkedList<A> LinkedList LinkedList<String> LinkedList class Test{ ... LinkedList ys=new LinkedList (); ys.add(“zero”); ys.add(“one”); String y = (String)ys.iterator().next();} Sostituzione con bound A Object cast GJ: esempio di traduzione 2 Es: interface Comparable{ public int compareTo (Object that);} class Byte implements Comparable { private byte value; public int compareTo (Byte that){ return this.value-that.value;} public int compareTo (Object that){ return this.compareTo((Byte)that);} ...} class Collections { public static Comparable max (Collection xs){ Iterator xi=xs.iterator(); ...}} A implements Comparable<A> Comparable<A> metodo bridge collega il metodo override dell’interfaccia con il metodo risultato nella traduzione della classe Comparable GJ: traduzione GJ non mantiene informazioni a run time sul tipo dei parametri ... maggiore efficienza di implementazione maggiore facilità nell’interfacciamento con il codice legacy minore espressività minore compatibilità con Java (classi e array) ... GJ è progettato per essere compatibile con estensioni che mantengano le informazioni sui tipi a run time GJ: traduzione cast-iron guarantee i metodi bridge possono essere inseriti direttamente nel bytecode sicurezza traduzione omogenea meno sicura rispetto all’eterogenea... ... ma la traduzione eterogenea combinata con il modello di sicurezza della JVM rende impossibili alcune istanziazioni GJ aggiunge metodi bridge quando una classe generica viene specializzata tramite inheritance Featherweight GJ FGJ: introduzione Estensione di Featherweight Java [IPW99] con supporto per tipi generici FJ calcolo funzionale minimale per Java computazionalmente completo ha solo 5 espressioni: variabile, creazione di un oggetto, accesso a field di oggetto, invocazione di metodo e cast esclude assegnamento, overloading, hiding, tipi primitivi ... per ottenere una dimostrazione semplice della soundness del type system FGJ: introduzione Le regole di type system di FGJ considerano: ¾ dichiarazione di classi creazione di oggetti accesso ai field di un oggetto variabili invocazione di metodi overriding di metodi (semplificato rispetto a GJ) ricorsione di metodi con this, subtyping e cast classi e metodi generici con bound ricorsivi Sono esclusi raw types e type inference per gli argomenti parametrici dei metodi FGJ non è un sottoinsieme proprio di GJ, anche se ne rispetta, in generale, la semantica FGJ: introduzione per FGJ, come per GJ, sono previsti due stili di implementazione diretto per cancellazione corrisponde alla traduzione eterogenea corrisponde alla traduzione omogenea le regole di type system vengono presentate rispetto a questi due stili FGJ: Sintassi CL ::= class C<X Y N> Y N {T f; K M} K ::= C(T f) {super(f); this.f = f;} M ::= <X Y N> T m (T x) { ↑e;} e ::= x | e.f | e.m<T>(e) | new N(e) | (N)e T ::= X | N N ::= C<T> classe parametrica costruttore metodo parametrico espressioni di FGJ variable type nonvariable type abbreviazioni: X = X1, ..., Xn; Y=extends; C=C<>, m=m<>, bound espliciti; e.m<T>(e): invocazione del metodo m con parametri T e argomenti e FGJ: bound e type environment Type environment U: X <: N mapping finito fra tipi variabile e tipi non variabile lega ogni variabile al suo bound Bound dei tipi boundU(T) = boundU(X)= U(X) boundU(N)= N upper bound di T in U FGJ: Subtype Subtype U |- T <: T riflessiva U |- S <: T T <: U U |- S <: U transitiva CT(C) = class C<X Y N> Y N {...} subtype per classe param. U |- C<T> <: [T/X] N (la sostituzione rispetta il bound) class table CT: C -> CL , mapping fra class name e class declaration invarianza dei parametri rispetto al subtyping T<:U ⇒ C<T> <: C<U> FGJ Tipi well-formed U |- Object ok X ∈ dom(U) U |- X ok CT(C) = class C<X Y N> Y N {...} U |- T ok U |- T <: [T/X] N U |- C<T> ok Tipo parametrico class table CT: C -> CL , mapping fra class name e class declaration U |- T ok = “il tipo T è well-formed nell’environment U” T <: [T/X] N = “la sostituzione di X con T rispetta il bound N” FGJ Class declaration X<:N |- N ok X<:N |- N ok X<:N |- T ok fields(N) = U g M OK IN C<X Y N> K = C(U g, T f) {super(g); this.f = f;} class C<XYN> Y N {T f; K M} OK Method declaration U= X<:N , Y<:O U|- T ok U|- T ok U|- O ok U|- S<:T U, x:T, this: C<X> |- e0 ∈ S CT(C)=class C<XYN> Y N { ... } override (m, N, <YYO> T → T) <YYO> T m (T x) {↑ e0;} OK in C<XYN> override (m, N, <YYO> T → T) “m è ridefinito in un sottotipo di N con tipo...” FGJ eterogeneo Method type lookup CT(C) = class C<X Y N> Y N {S f; K M} <Y Y O> U m (U x) {↑ e;} ∈ M mtype(m, C<T>) = [T/X](<Y Y O> U → U) metodo dichiarato nella classe CT(C) = class C<X Y N> Y N {S f; K M} m is not defined in M mtype(m,C<T>) = mtype(m,[T/X]N) metodo ereditato equivalentemente: mbody(m<V>,N)=(x,e) con V parametri, x argomenti, e espressione FGJ eterogeneo Expression typing: invocazione di un metodo U,Γ |- e0 ∈ T0 mtype(m, boundU(T0)) = (<Y Y O> U → U) U|- V <: [V/Y] O U |- V ok U;Γ |- e∈S U|- S <: [V/Y] U U;Γ |- e0.m<V>(e) ∈ [V/Y] U Γ(x) enviroment che dà il mapping per le variabili U;Γ |- e∈T : “nell’environment U e nell’environment Γ, l’espressione e ha tipo T” FGJ: reduction rules accesso ai campi fields(N)=T f (new N(e)).fi → ei invocazione di metodi mbody(m<V>,N)=(x,e0) new(N(e)).m<V>(d) → [d/x, new N(e)/this]e0 casting ∅ |- N<:O (O)(new N(e)) → new N(e) FGJ eterogeneo: proprietà Types substitution preserves subtyping U1|- U <: [U/X]N Se U1, X <: N, U2|- S<:T e con U1|- U ok e nessuno degli X compare in U1 allora U1, [U/X]U2 |- ,[U/X]S<: ,[U/X]T Types substitution preserves typing U1|- U <: [U/X]N Se U1, X <: N, U2; Γ|- e∈T e con U2|- U ok e nessuno degli X compare in U1 allora U1, [U/X]U2; [U/X]Γ |- e ∈ S per qualche S tc. U1, [U/X]U2 |- S<:[U/X]T Terms substitution preserves typing Se U; Γ, x:T |- e∈T e U; Γ|- d∈S con U|- S<:T allora U; Γ|- [d/x]e∈S per qualche S tc U|- S<:T FGJ eterogeneo: proprietà Subject reduction Se U; Γ|- e∈T e e → e’ allora U; Γ|- e’∈T’ per qualche T tc U|- T’ <:T Se un termine è ben tipato, si riduce a un secondo termine, anch’esso ben tipato, il cui tipo è sottotipo del primo Progress Data e espressione well-typed (1) se e include la sottoespressione new N0(e).f allora fields(N0)=T f e f ∈ f (2) se e include la sottoespressione new N0(e).m<V>(d) allora mbody(m<V>,N0)=(x ,e0) e #(x)= #(d) L’unico modo in cui un programma FGJ può avere un errore è nel caso in cui si effettui un cast, in particolare un downcast o uno stupid cast FGJ eterogeneo: proprietà Backward Compatibility Se un programma (e,CT) scritto in FJ è ben tipato secondo le regole di tipo di FJ, allora è ben tipato secondo le regole di FGJ FGJ by erasure modella il vero comportamento di GJ, che non mantiene tipi a run time programmi FGJ vengono tradotti in FJ by erasure, (per cancellazione o traduzione omogenea) proprietà dell’erasure programmi FGJ ben tipati vengono tradotti in programmi FJ ben tipati la traduzione rispetta il comportamento del programma FGJ: traduzione dei tipi Per effettuare la traduzione di un tipo, vengono rimossi i parametri che compaiono nel tipo e le variabili di tipo vengono sostituite con le traduzioni dei loro bound Traduzione di un tipo: |T|U |T|U= C se boundU(T)=C<T> FGJ: traduzione delle espressioni il tipo delle espressioni viene utilizzato per decidere quali cast inserire funzioni ausiliarie fieldmax e mtypemax che determinano il tipo di un campo o di un metodo nella superclasse più alta in cui sono stati definiti (per subclassing di classi parametriche istanziate) Traduzione di un’espressione: |e|U,Γ e una qualsiasi espressione della sintassi, ben tipata rispetto agli environment U e Γ FGJ: traduzione delle espressioni Traduzione di una variabile |x|U,Γ=x Traduzione dell’accesso a un field U;Γ|- e0.f∈T U;Γ|- e0∈T0 fieldsmax(|T0|U)(f) = |T|U | e0.f |U,Γ= | e0|U,Γ.f U;Γ|- e0.f∈T U;Γ|- e0∈T0 fieldsmax(|T0|U)(f) ≠ |T|U | e0.f |U,Γ= (|T|U) | e0|U,Γ.f cast FGJ: traduzione delle espressioni Traduzione dell’invocazione di un metodo U;Γ|- e0∈T0 U;Γ|- e0.m<V>(e) ∈ T mtypemax (m,|T0|U) = C →D e D=|T|U | e0.m<V>(e) |U,Γ= | e0|U,Γ.m(|e| U,Γ) U;Γ|- e0.m<V>(e)∈T U;Γ|- e0∈T0 mtypemax (m,|T0|U) = C →D e D = |T|U | e0.m<V>(e) |U,Γ= (|T|U) | e0|U,Γ.m(|e| U,Γ) FGJ: traduzione delle espressioni Traduzione dell’istanziazione |new N(e)|U,Γ= new|N|U,(|e| U,Γ) Traduzione del cast |(N)(e0)|U,Γ= (|N|U)(|e0| U,Γ) FGJ: traduzione dei metodi Traduzione di un metodo: |M|U,C Γ=x:T U’= U,Y<:O e’=[(|T|U’)x’/x]|e|U’,Γ mtypemax(m,C)=D→D |<YYO> T m (T x) {↑e;}|U,C=D m (D x’){↑e’;} Traduzione di un costruttore: |K|U,C |C(U g,T f) {super(g); this.f=f}|U,C=C(fieldsmax(C)) {super(g); this.f=f} FGJ: traduzione di una classe Traduzione di una classe U= U,X <: N |class C<XYN> YN {T f; K M}|U,C=class CY|N|U {|T|U f; |K|U |M|U,C} FGJ: proprietà della traduzione La traduzione preserva il tipaggio Se una class table CT di FGJ è ok e U,Γ|- e∈T allora |Γ|U|- |e|U,Γ ∈|T|U e |CT| è ok secondo le regole di FJ La traduzione preserva la riduzione Se U,Γ|- e∈T e e →FGJ*e’ allora esiste qualche espressione FJ d’ tale che d’ è ottenuta da e mediante la combinazione di aggiunta di upcast, rimozione di cast o sostituzione di cast con cast più ampi (|e’|U,Γ ⇒ d’) e |e|U,Γ →FJ d’ e e’ * FGJ |e’| |e| FJ d’ FGJ: proprietà della traduzione La traduzione preserva i risultati dell’esecuzione Se U,Γ|- e ∈T e e →FGJ* v, con v espressione completamente valutata, allora |e|U,Γ →FJ |v|U,Γ La traduzione preserva gli errori di typecast Se U,Γ|- e∈T e e →FGJ* e’ e e’ contiene un’espressione che genera errore (C<S>) new D<T>(e) allora |e|U,Γ →FJ* d’ contiene un’espressione sbagliata (C) new D(d) con d espansione della traduzione di e, nelle stesse posizioni della traduzione di e’ Bibliografia [BOSW98] Gilad Bracha, Martin Odersky, David Stoutamire, and Philip Wadler. Making the future safe for the past: Adding genericity to the Java programming language (OOPSLA98) [GJc] GJ and Pizza compilers and documentation: www.cs.belllabs.com/~wadler/pizza/gj [Java] www.java.sun.com http://developer.java.sun.com/developer/technicalArticles/releases/ generics/ [IPW99] Atsushi Igarashi, Benjamin Pierce, and Philip Wadler. Featherweight Java: A minimal Core Calculus for Java and GJ (OOPSLA99) [OW97] Martin Odersky and Philip Wadler. Pizza into Java: Translating theory into practice (POPL97) Bibliografia Altre proposte per estensioni di Java con generici [AFM97] Agesen, Freund, Mitchell. Adding Parametrized Types to Java (OOPSLA97) [CS98] Cartwright, and Steele. Compatible Genericity with run-time types for the Java Programming Language (OOPSLA98) [MBL97] Myers, Bank, and Liskow. Parametrized Types for Java (POPL97) [TT98] Thorup, and Togersen. Structural virtual types. Informal Session on Types for Java.