Mehrere Scala-Eigenheiten interagieren, um dieses Verhalten zu erzielen. Die erste Sache ist, dass Manifest
s nicht nur an die geheime implizite Parameterliste im Konstruktor angehängt werden, sondern auch an die Kopiermethode. Es ist bekannt, dass
case class Foo[+A : Manifest](a: A)
für
case class Foo[+A](a: A)(implicit m: Manifest[A])
nur syntaktischer Zucker ist, aber dies wirkt sich auch auf den Kopierkonstruktor, die wie dieser
def copy[B](a: B = a)(implicit m: Manifest[B]) = Foo[B](a)(m)
aussehen würde All diese implicit m
s werden von th erstellt Der Compiler wird über die implizite Parameterliste an die Methode gesendet.
Das wäre in Ordnung, solange man die copy
-Methode an einem Ort verwendet, an dem der Compiler Foo
s Typparameter kannte. Zum Beispiel wird dies außerhalb der Bar-Klasse arbeiten:
val foo = Foo(1)
val aCopy = foo.copy()
println(aCopy.myManifest) // Prints "Int"
Das funktioniert, weil der Compiler folgert, dass foo
ist ein Foo[Int]
so weiß er, dass foo.a
ist ein Int
so kann es copy
wie folgt aufrufen:
val aCopy = foo.copy()(manifest[Int]())
(Beachten Sie, dass manifest[T]()
ist eine Funktion, die eine offensichtliche Darstellung des Typs ist T
, zB Manifest[T]
mit einem großen „M“. Nicht gezeigt erzeugt das eine dition des Standardparameters in copy
.) Es funktioniert auch innerhalb der Foo
Klasse, weil es bereits das Manifest enthält, das beim Erstellen der Klasse übergeben wurde. Es würde wie folgt aussehen:
case class Foo[+A : Manifest](a: A) {
def myManifest = implicitly[Manifest[_ <: A]]
def localCopy = copy()
}
val foo = Foo(1)
println(foo.localCopy.myManifest) // Prints "Int"
Im ursprünglichen Beispiel wird jedoch nicht in der Bar
Klasse wegen der zweiten Besonderheit: Während die Parameter des Typs von Bar
innerhalb der Bar
Klasse bekannt sind, die Typparameter die Typparameter sind nicht. Er weiß, dass A
in Bar
ein Foo
oder ein SubFoo
oder SubSubFoo
ist, aber nicht, wenn es ein Foo[Int]
oder ein Foo[String]
ist. Dies ist natürlich das bekannte Typ-Löschproblem in Scala, aber es scheint hier ein Problem zu sein, selbst wenn es nicht so aussieht, als würde die Klasse irgendetwas mit dem Typ foo
s Typparameter tun. Aber denken Sie daran, dass jedes Mal, wenn copy
aufgerufen wird, eine geheime Injektion eines Manifests vorliegt, und diese Manifeste überschreiben diejenigen, die vorher dort waren.Da die Bar
Klasse war keine Ahnung hat, der Typ-Parameter von foo
ist, schafft es nur ein Manifest von Any
und sendet diese zusammen wie folgt aus:
def fooCopy = foo.copy()(manifest[Any])
Wenn man die Kontrolle über die Foo
Klasse (zB es ist nicht List
) dann eine Abhilfe durch all das Kopieren über in der Foo-Klasse zu tun, indem ein Verfahren hinzufügen, die das richtige Kopieren tun werden, wie localCopy
oben und gibt das Ergebnis:
case class Bar[A <: Foo[Any]](foo: A) {
//def fooCopy = foo.copy()
def fooCopy = foo.localCopy
}
val bar = Bar(Foo(1))
println(bar.fooCopy.myManifest) // Prints "Int"
Eine andere Lösung ist Foo
s Typparameter als manifestierten Typ-Parameter von Bar
hinzuzufügen:
case class Bar[A <: Foo[B], B : Manifest](foo: A) {
def fooCopy = foo.copy()
}
Aber diese skaliert schlecht, wenn Klassenhierarchie groß ist, (d mehr Mitglieder haben Typparameter und diese Klassen haben auch Typparameter, da jede Klasse die Typparameter jeder darunter liegenden Klasse haben muss. Es scheint auch die Typinferenz zu machen ausflippen, wenn ein Bar
zu konstruieren versucht:
val bar = Bar(Foo(1)) // Does not compile
val bar = Bar[Foo[Int], Int](Foo(1)) // Compiles
Gute Arbeit, mein Freund! –