2013-03-03 29 views
49

Qual è la differenza tra le seguenti definizioni Generics a Scala:scala - Qualsiasi vs sottolineatura nel generici

class Foo[T <: List[_]] 

e

class Bar[T <: List[Any]] 

Il mio istinto mi dice che sono circa lo stesso, ma che quest'ultimo è più esplicito. Sto trovando casi in cui il precedente compila, ma quest'ultimo non lo fa, ma non riesco a mettere il dito sulla differenza esatta.

Grazie!

Edit:

Posso lanciare un altro nel mix?

class Baz[T <: List[_ <: Any]] 
+5

Vincolare un tipo a "<: Any" non cambia mai nulla. Ogni tipo in Scala è '<: Any'. –

risposta

64

OK, ho pensato che avrei dovuto prendere la mia opinione, invece di pubblicare solo commenti. Scusa, questo sarà lungo, se vuoi che il TLDR salti alla fine.

Come ha detto Randall Schulz, qui _ è una scorciatoia per un tipo esistenziale. Vale a dire,

class Foo[T <: List[_]] 

è una scorciatoia per

class Foo[T <: List[Z] forSome { type Z }] 

nota che, contrariamente a ciò che la risposta di Randall Shulz menziona (full disclosure: ho sbagliato troppo in una versione precedente fo questo post, grazie a Jesper Nordenberg per segnalarlo) non questo lo stesso come:

class Foo[T <: List[Z]] forSome { type Z } 

né è la stessa:

class Foo[T <: List[Z forSome { type Z }] 

Attenzione, è facile sbagliarlo (come mostra il mio precedente show): l'autore dell'articolo a cui fa riferimento la risposta di Randall Shulz si è sbagliato (vedi commenti) e lo ha corretto in seguito. Il mio problema principale con questo articolo è che nell'esempio mostrato, l'uso di existentials dovrebbe salvarci da un problema di digitazione, ma non è così. Vai a controllare il codice e prova a compilare compileAndRun(helloWorldVM("Test")) o compileAndRun(intVM(42)). Sì, non si compila. Semplicemente facendo compileAndRun generico in A renderebbe il codice compilare, e sarebbe molto più semplice. In breve, questo non è probabilmente il miglior articolo per conoscere gli esistenziali e per cosa sono utili (l'autore stesso riconosce in un commento che l'articolo "ha bisogno di essere riordinato").

Quindi mi consiglia di leggere questo articolo: http://www.artima.com/scalazine/articles/scalas_type_system.html, in particolare le sezioni denominate "Tipi esistenziali" e "Varianza in Java e Scala".

Il punto importante che dovresti ottenere da questo articolo è che gli elementi esistenziali sono utili (oltre ad essere in grado di gestire classi java generiche) quando si tratta di tipi non covarianti. Ecco un esempio.

case class Greets[T](private val name: T) { 
    def hello() { println("Hello " + name) } 
    def getName: T = name 
} 

Questa classe è generico (si noti, inoltre, che è invariante), ma possiamo vedere che hello in realtà non fanno uso del parametro type (a differenza getName), quindi se ho un'istanza di Greets I dovrebbe sempre essere in grado di chiamarlo, qualunque sia lo T. Se voglio definire un metodo che accetta un'istanza Greets e proprio chiama il suo metodo hello, ho potuto provare questo:

def sayHi1(g: Greets[T]) { g.hello() } // Does not compile 

Certo, basta, questo non si compila, come T viene fuori dal nulla qui.

OK, allora, facciamo il metodo generico:

def sayHi2[T](g: Greets[T]) { g.hello() } 
sayHi2(Greets("John")) 
sayHi2(Greets('Jack)) 

Grande, questo funziona. Potremmo anche utilizzare gli elementi esistenziali qui:

def sayHi3(g: Greets[_]) { g.hello() } 
sayHi3(Greets("John")) 
sayHi3(Greets('Jack)) 

Anche lavori. Quindi, tutto sommato, non vi è alcun vantaggio reale dall'usare un parametro esistenziale (come in sayHi3) su tipo (come in sayHi2).

Tuttavia, questo cambia se Greets appare come parametro di tipo in un'altra classe generica. Supponiamo, ad esempio, di archiviare più istanze di Greets (con diverso T) in un elenco. Proviamo:

val greets1: Greets[String] = Greets("John") 
val greets2: Greets[Symbol] = Greets('Jack) 
val greetsList1: List[Greets[Any]] = List(greets1, greets2) // Does not compile 

L'ultima riga non viene compilato perché Greets è invariante, quindi un Greets[String] e Greets[Symbol] non può essere trattata come un Greets[Any] anche se String e Symbol entrambi estende Any.

OK, proviamo con una esistenziali, utilizzando la notazione abbreviata _:

val greetsList2: List[Greets[_]] = List(greets1, greets2) // Compiles fine, yeah 

Questo compila bene, e si può fare, come ci si aspettava:

greetsSet foreach (_.hello) 

Ora, ricordate che la ragione abbiamo avuto un problema di controllo del tipo in primo luogo perché Greets è invariante. Se è stato trasformato in una classe covariante (class Greets[+T]), allora tutto avrebbe funzionato fuori dalla scatola e non avremmo mai avuto bisogno di esistenziali.


Quindi, per riassumere, esistenziali sono utili per affrontare con le classi generiche invarianti, ma se la classe generica non ha bisogno di apparire se stesso come un parametro di tipo ad un altro classe generica, è probabile che non è necessario esistenziali e la semplice aggiunta di un parametro di tipo per il metodo funzionerà

Ora tornare (finalmente, lo so!) alla tua domanda specifica, per quanto riguarda

class Foo[T <: List[_]] 

Perché List è covariante, questo è a tutti gli effetti e purp È come dire:

class Foo[T <: List[Any]] 

Quindi, in questo caso, l'uso di entrambe le notazioni è davvero solo una questione di stile.

Tuttavia, se si sostituisce List con Set, le cose cambiano:

class Foo[T <: Set[_]] 

Set è invariante, e quindi ci sono nella stessa situazione con la classe Greets dal mio esempio. Quindi quanto sopra è molto diverso da

class Foo[T <: Set[Any]] 
+0

No, 'classe Foo [T <: List [_]]' è una scorciatoia per 'class Foo [T <: List [Z] forSome {type Z}]'. Allo stesso modo, "Lista [Greets [_]]" è una scorciatoia per 'List [Greets [Z] forSome {type Z}]' (e non 'List [Greets [Z]] forSome {type Z}'). –

+0

Doh, stupido! Grazie, ho risolto questo. Abbastanza divertente, il mio primo pensiero è stato quello di verificare l'articolo di David R MacIver (http://www.drmaciver.com/2008/03/existential-types-in-scala/) che parla esattamente della stenografia delle esistenziali e mette in guardia il loro intuitivo desugaring. Il fatto è che sembra aver sbagliato se stesso. In realtà, quello che è successo è che il modo in cui è stato fatto il desugaring è cambiato poco dopo il suo articolo (in scala 2.7.1, vedi registro delle modifiche su http://www.scala-lang.org/node/43#2.8.0). Immagino che questo cambiamento partecipi alla confusione. –

+0

Bene, è facile mescolare il significato della sintassi del tipo esistenziale. Almeno l'attuale desugaring è l'IMHO più logico. –

6

Il primo è una scorciatoia per un tipo esistenziale, quando il codice non ha bisogno di sapere qual è il tipo è o vincolarlo:

class Foo[T <: List[Z forSome { type Z }] 

Questa forma dice che il tipo di elemento di List è sconosciuto a class Foo piuttosto che al tuo secondo modulo, il quale specifica in particolare che il tipo di elemento di List è Any.

Dai un'occhiata a questa breve spiegazione blog article sui tipi esistenziali in Scala.

+3

Mentre si spiega che cos'è un esistenziale, penso che la domanda non sia quella che gli esistenziali sono in generale, ma c'è una reale differenza osservabile tra 'T [_]' (che è un caso particolare di uso esistenziale) e 'T [Qualsiasi] '? –

+0

Ovviamente c'è. Il blog di cui ho parlato ne ha una bella illustrazione. –

+3

Preferisco avere ** la tua ** risposta menziona come la covarianza sia essenziale per la differenza tra 'T [_]' e 'T [Any]', poiché è al centro della domanda "perché usi anche' T [_] 'su' T [Qualsiasi] '". Inoltre, nella sua domanda Sean Connolly menzionava esplicitamente 'Lista [_]'. Dato che 'List' è in realtà covariante, ci si può chiedere se ** in questo caso ** ci sia davvero una differenza tra' List [_] 'e' List [Any] '. –