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]]
Einen Typ zu beschränken, um '<: Any 'zu sein, ändert nie etwas. Jede Art von Scala ist '<: Any '. –