2009-08-31 9 views
28

Ho recentemente iniziato ad imparare Scala e sono rimasto deluso (ma non sorpreso) del fatto che i loro prodotti generici sono implementati anche tramite cancellazione dei tipi.È possibile che Scala abbia generici reificati senza modificare la JVM?

La mia domanda è, è possibile per Scala avere generici reificati, o la JVM deve essere modificata in qualche modo? Se la JVM deve essere cambiata, che cosa dovrebbe essere cambiata esattamente?

risposta

22

No: non è possibile eseguire Scala come bytecode Java equivalente se il bytecode non supporta i generici reificati.

Quando si chiede "cosa è che deve essere modificato?", la risposta è: specifica bytecode. Attualmente il bytecode non consente di definire il tipo parametrizzato di una variabile. È stato deciso che una modifica al bytecode per supportare i generici reificati sarebbe break backwards compatibility, ovvero generics would have to be implemented via type erasure.

Per aggirare il problema, Scala ha usato il potere della sua implicit meccanismo per definire un Manifest che può essere importato in qualsiasi ambito di scoprire informazioni di tipo in fase di esecuzione. I manifesti sono sperimentali e in gran parte non documentati ma sono are coming as part of the library in 2.8. Ecco un'altra buona risorsa su Scala reified generics/Manifests

+0

Eventuali collegamenti dove posso leggere su questo? – cdmckay

+0

Collegamenti aggiunti mentre parli :-) –

+0

Non avevo idea che i ragazzi della Scala lo stessero facendo. Questo è molto bello – cdmckay

3

"Manifesto implicito" è un trucco del compilatore di Scala e non rende i generici in Scala reificati. Il compilatore Scala, quando vede una funzione con parametro "implicito m: Manifest [A]" e conosce il tipo generico di A nel sito di chiamata, verrà inserita la classe di A ei suoi parametri di tipo generico in un Manifesto e renderlo disponibile all'interno della funzione. Tuttavia, se non riusciva a capire il vero tipo di A, allora non ha modo di creare un Manifesto. In altre parole, il Manifest deve essere passato lungo la funzione che chiama catena se la funzione interiore ne ha bisogno.

scala> def typeName[A](a: A)(implicit m: reflect.Manifest[A]) = m.toString 
typeName: [A](a: A)(implicit m: scala.reflect.Manifest[A])java.lang.String 

scala> typeName(List(1)) 
res6: java.lang.String = scala.collection.immutable.List[int] 

scala> def foo[A](a: A) = typeName(a) 
<console>:5: error: could not find implicit value for parameter m:scala.reflect.Manifest[A]. 
     def foo[A](a: A) = typeName(a) 
           ^

scala> def foo[A](a: A)(implicit m: reflect.Manifest[A]) = typeName(a) 
foo: [A](a: A)(implicit m: scala.reflect.Manifest[A])java.lang.String 

scala> foo(Set("hello")) 
res8: java.lang.String = scala.collection.immutable.Set[java.lang.String] 
+0

Perché non aggiungono semplicemente un Manifesto ad ogni chiamata di metodo che ha generici? I manifesti comportano una penalità per le prestazioni? – cdmckay

+0

Non sono sicuro che qualcuno qui abbia detto che i manifesti rendono i generici reificati (che sembra essere la tua implicazione). Ho detto che i manifesti possono essere importati per ricavare informazioni sul tipo in fase di esecuzione, mentre i generici reificati richiederebbero una modifica alle specifiche del bytecode. –

+0

@oxbow_lakes Mi dispiace se la mia risposta si è presentata in questo modo; Non volevo insinuare che qualcuno dicesse qualcosa su "i manifesti rendono i generici reificati". Tutto ciò che volevo sottolineare sono alcune limitazioni all'uso di Manifest. –

0

volta scalac è un compilatore, ha il potenziale di essere in grado di impreziosire il codice generato con qualunque strutture di dati sono necessari per implementare generici reificate.

Quello che voglio dire è che scalac avrebbe la capacità di vedere ...

// definition 
class Klass[T] { 
    value : T 
} 

//calls 
floats = Klass[float] 
doubles = Klass[double] 

... e "espandere" a qualcosa di simile:

// definition 
class Klass_float { 
    value : float 
} 
class Klass_double { 
    value : double 
} 

// calls 
floats = Klass_float 
doubles = Klass_double 

Modifica

Il punto è: il compilatore ha la capacità di creare tutte le strutture dati necessarie che dimostrano essere necessario per fornire ulteriori informazioni sul tipo in fase di esecuzione. Una volta che questo tipo di informazioni è disponibile, il runtime di Scala ne trarrebbe vantaggio e potrebbe eseguire tutte le operazioni di riconoscimento dei tipi che possiamo immaginare. Non importa se la JVM fornisce bytecode per generici reificati o meno. Il lavoro non è svolto dalla JVM, ma dalla biblioteca di Scala.

Se hai già scritto un debugger simbolico (l'ho fatto!), si sa che si può sostanzialmente "scaricare" tutte le informazioni che il compilatore ha in fase di compilazione nel file binario generato, adottando qualsiasi organizzazione di dati dimostra essere più conveniente per un'ulteriore elaborazione. Questa è esattamente la stessa idea: "scarica" ​​tutte le informazioni sul tipo presenti nel compilatore Scala.

In breve, non vedo perché non sia possibile, non importa se la JVM fornisce operazioni native per generici reificati o meno.

Un'altra modifica

IBM X10 dimostra la capacità sto parlando di: compila il codice X10 sul codice Java, sfruttando i generici reificati su piattaforme Java. Come ho detto prima: può essere fatto, ma solo da persone che sanno come funziona un compilatore e come una libreria di runtime può sfruttare le informazioni "scaricate" dal compilatore in fase di compilazione sul codice generato. Per ulteriori informazioni: http://x10.sourceforge.net/documentation/papers/X10Workshop2012/slides/Takeuchi.pdf

+0

Questo ha due problemi. Innanzitutto, moltiplica le dimensioni del tuo codice. In secondo luogo, non si conoscono tutte le classi che si devono generare in fase di compilazione. Prendiamo, per esempio, un'implementazione di raccolta dalla libreria che tu normalmente/potenzialmente hai solo in forma compilata. Ti aspetteresti che funzioni con la tua nuova classe 'RichardsFoo', vero? – Raphael

+0

Questo è facilmente aggirato memorizzando la classe compilata _erased_. Quindi, quando scalac incontra Klass [SomethingStrange], ottiene Klass cancellato dal tipo e crea un'istanza di Klass_SomethingStrange. Ecco come funziona .NET in fase di runtime, BTW. – Cyberax

+1

@Raphael: il fatto che moltiplica la dimensione del codice è un problema solo se si considera il costo/beneficio negativo. Secondo, per favore guarda http://fastutil.di.unimi.it/. Un esempio mostra che esistono implementazioni specializzate in cima a un'API generica. Il problema qui è la moltiplicazione della dimensione del codice. Come ho detto prima, potrebbe essere o non essere un problema. –

3

Per completare la risposta di oxbow_lakes: Non è possibile e sembra che non accadrà mai (almeno presto).

L'(confutabile) Ragioni JVM non supporterà generici reificati sembra essere:

  • Lower prestazioni.
  • Interrompe la compatibilità con le versioni precedenti. Può essere risolto duplicando e risolvendo molte librerie.
  • Può essere implementato utilizzando manifesti: la "soluzione" e il più grande impedimento.

Riferimenti:

Puoi facilmente confrontarlo e vedere che p l'impatto dell'erosione è molto evidente . Soprattutto il consumo di memoria aumenta molto.

Credo che la via da seguire sia quella di avere reificazione opzionale nel modo in cui inizia a fare in Scala con Manifests/TypeTags.

Se è possibile e combinarlo con la specializzazione runtime, è possibile puntare a prestazioni elevate e codice generico . Tuttavia, questo è probabilmente l'obiettivo per Scala 2.12 o 2.13.

+0

+1 per la citazione di Odersky. Sono assolutamente d'accordo. – Ingo

Problemi correlati