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.