2013-03-03 10 views
49

Was der Unterschied zwischen den folgenden Generics Definitionen in Scala ist:scala - Alle vs Strich in Generika

class Foo[T <: List[_]] 

und

class Bar[T <: List[Any]] 

Mein Bauchgefühl sagt mir, dass sie in etwa gleich sind, sondern dass letztere ist expliziter. Ich finde Fälle, in denen der erstere kompiliert, aber der letztere nicht, aber kann nicht den genauen Unterschied aufzeigen.

Danke!

Edit:

Kann ich werfen ein anderes in den Mix?

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

Einen Typ zu beschränken, um '<: Any 'zu sein, ändert nie etwas. Jede Art von Scala ist '<: Any '. –

Antwort

64

OK, dachte ich, ich sollte mein nehmen auf sie haben, anstatt nur Abgabe von Kommentaren. Tut mir leid, das wird lange dauern, wenn Sie möchten, dass das TLDR zum Ende springt.

Als Randall Schulz sagte, hier _ eine Abkürzung für einen existentiellen Typen ist. Das heißt,

class Foo[T <: List[_]] 

ist eine Abkürzung für

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

Beachten Sie, dass im Gegensatz zu dem, was Randall Shulz Antwort erwähnt (vollständige Offenlegung: Ich habe es auch falsch in einer früheren Version fo diesen Beitrag dank Jesper Nordenberg für it out) nicht das gleiche zeigt wie:

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

noch es das gleiche wie:

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

Vorsicht, es ist leicht, es falsch zu machen (wie mein früherer Gauner zeigt): der Autor des von Randall Shulz angesprochenen Artikels hat es selbst falsch gemacht (siehe Kommentare), und später behoben. Mein Hauptproblem mit diesem Artikel ist, dass in dem gezeigten Beispiel die Verwendung von Existenzen uns vor einem Tippproblem bewahren soll, aber nicht. Überprüfen Sie den Code und versuchen Sie, compileAndRun(helloWorldVM("Test")) oder compileAndRun(intVM(42)) zu kompilieren. Ja, kompiliert nicht. Einfach machen compileAndRun generische in A würde den Code kompilieren, und es wäre viel einfacher. Kurz gesagt, das ist wahrscheinlich nicht der beste Artikel, um etwas über Existenziale zu lernen und wozu sie gut sind (der Autor selbst bestätigt in einem Kommentar, dass der Artikel "aufgeräumt werden muss").

Also ich würde eher empfehlen, diesen Artikel zu lesen: http://www.artima.com/scalazine/articles/scalas_type_system.html, insbesondere die Abschnitte namens "Existential Typen" und "Varianz in Java und Scala".

Der wichtige Punkt, den Sie von diesem Artikel erhalten sollten, ist, dass Existenzen nützlich sind (abgesehen davon, dass Sie mit generischen Java-Klassen umgehen können), wenn Sie mit nichtkovarianten Typen arbeiten. Hier ist ein Beispiel.

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

Diese Klasse ist generisch (beachten Sie auch, dass invariant ist), aber wir können sehen, dass hello wirklich nicht die Verwendung der Typ-Parameter (im Gegensatz zu getName) machen, also wenn ich eine Instanz von Greets mir sollte immer in der Lage sein, es zu nennen, was auch immer T ist. Wenn ich eine Methode definieren möchten, die eine Greets Instanz nimmt und nur nennt seine hello Methode, konnte ich dies versuchen:

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

Sicher genug, führt dies nicht kompilieren, wie T nirgends hier aus kommt.

OK dann lassen Sie uns die Methode machen generic:

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

Große, das funktioniert. Wir könnten auch Existenziale hier verwenden:

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

Funktioniert auch. Insgesamt ergibt sich also kein wirklicher Nutzen durch die Verwendung eines existentiellen (wie in sayHi3) Über-Typparameters (wie in sayHi2).

Dies ändert sich jedoch, wenn Greets sich selbst als Typparameter einer anderen generischen Klasse erscheint. Angenommen, wir möchten mehrere Instanzen von Greets (mit verschiedenen T) in einer Liste speichern. Versuchen wir es:

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

Die letzte Zeile nicht kompilieren, weil Greets unveränderlich ist, so ein Greets[String] und Greets[Symbol] kann nicht als Greets[Any] behandelt werden, obwohl String und Symbol beide Any erstreckt.

OK, lassen Sie uns mit einer existentiellen versuchen, die Kurzschreibweise mit _:

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

Dies kompiliert gut, und Sie tun können, wie erwartet:

greetsSet foreach (_.hello) 

Nun, denkt daran, dass der Grund Wir hatten ein Typ-Checking-Problem in erster Linie, weil Greets invariant ist. Wenn es in eine kovariante Klasse umgewandelt wurde (class Greets[+T]), dann hätte alles aus der Box heraus gearbeitet und wir hätten nie Existenzen benötigt.


So zusammenzufassen, Existenziale sind nützlich mit generischen invarianten Klassen umgehen, aber wenn die generische Klasse selbst nicht erscheinen als Typ-Parameter auf einem andere generische Klasse benötigt, stehen die Chancen, dass Sie nicht brauchen, Existenziale und einfach einen Typparameter auf Ihre Methode wird das Hinzufügen

Jetzt kommen wieder

arbeiten (endlich, ich weiß!), um Ihre spezielle Frage in Bezug auf

class Foo[T <: List[_]] 

Da List covariant ist, dann ist dies für alle Absichten und purp ose die gleiche wie nur sagen:

class Foo[T <: List[Any]] 

So in diesem Fall entweder Notation ist wirklich nur eine Frage des Stils.

Wenn Sie jedoch List mit Set ersetzen, die Dinge zu ändern:

class Foo[T <: Set[_]] 

Set unveränderlich ist und somit sind wir in der gleichen Situation wie bei der Greets Klasse von meinem Beispiel. Somit ist das obige wirklich sehr verschieden von

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

Nein, 'Klasse Foo [T <: Liste [_]]' ist eine Abkürzung für 'Klasse Foo [T <: Liste [Z] fürSome {Typ Z}]'. Der gleiche Weg 'List [Greets [_]]' ist eine Abkürzung für 'List [Greets [Z] forSome {type Z}]' (und nicht 'List [Greets [Z]] fürSome {type Z}'). –

+0

Doh, dumme mich! Danke, das habe ich behoben. Lustig genug, war meine erste aber, um den Artikel von David R. MacIver (http://www.drmaciver.com/2008/03/existential-types-in-scala/) zu überprüfen, der genau über die Kurzschreibweise der Existenz spricht und davor warnt ihre nicht intuitive Entschuldung. Die Sache ist, er scheint es selbst falsch zu haben. Was tatsächlich passiert ist, ist, dass sich die Entzuckerung kurz nach seinem Artikel geändert hat (in Scala 2.7.1, siehe Change Log unter http://www.scala-lang.org/node/43#2.8.0). Ich denke, diese Veränderung trägt zur Verwirrung bei. –

+0

Nun, es ist leicht, die Bedeutung der existentiellen Syntax zu verwechseln. Zumindest die aktuelle Entschuldigung ist die logischste IMHO. –

6

Ersteres ist eine Abkürzung für eine existenzielle Art, wenn der Code muss nicht wissen, was der Typ ist oder es einschränken:

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

Diese Form sagt, dass der Elementtyp List ist unbekannt zu class Foo eher als Ihre zweite Form, die speziell besagt, dass die List 's Elementtyp ist Any.

Schauen Sie sich diese kurze Erläuterungs blog article auf existentielle Typen in Scala.

+3

Während Sie erklären, was ein Existenzielles ist, denke ich, ist die Frage nicht, was Existenzen im Allgemeinen sind, aber gibt es einen tatsächlich beobachtbaren Unterschied zwischen 'T [_]' (was besonderen Fall des existentiellen Gebrauchs ist) und 'T [Beliebige] '? –

+0

Natürlich gibt es. Der Blog, von dem ich gesprochen habe, hat eine schöne Illustration davon. –

+3

Ich möchte ** ** Ihre ** Antwort lieber erwähnen, wie wichtig die Kovarianz für den Unterschied zwischen 'T [_]' und 'T [Any]' ist, wie es im Kern der Frage "Warum verwenden Sie' T [_] 'über' T [Beliebige] '". Außerdem hat Sean Connolly in seiner Frage ausdrücklich "List [_]" erwähnt. Da "List" tatsächlich kovariant ist, kann man sich fragen, ob ** in diesem Fall wirklich ein Unterschied zwischen "List [_]" und "List [Any]" besteht. –