So lassen Sie uns sagen, dass ich eine Klasse mit einer kontraTypParameter haben:Ärger mit Typ Varianz
trait Storage[-T] {
def list(path: String): Seq[String]
def getObject[R <: T](path: String): Future[R]
}
Die Idee vom Typ Parameter ist die Umsetzung an der oberen Grenze der Arten zu beschränken, die sie zurückkehren können. So kann Storage[Any]
alles lesen, während Storage[avro.SpecificRecord]
avro Aufzeichnungen lesen, aber nicht andere Klassen:
def storage: Storage[avro.SpecificRecord]
storage.getObject[MyAvroDoc]("foo") // works
storage.getObject[String]("bar") // fails
Jetzt habe ich eine Utility-Klasse, die verwendet werden kann, durch Objekte in einem bestimmten Ort zu iterieren:
class StorageIterator[+T](
val storage: Storage[_ >: T],
location: String
)(filter: String => Boolean) extends AbstractIterator[Future[T]] {
val it = storage.list(location).filter(filter)
def hasNext = it.hasNext
def next = storage.getObject[T](it.next)
}
Dies funktioniert, aber manchmal muß ich die zugrunde liegenden storage
vom Iterator zugreifen, stromabwärts eine andere Art von Objekt aus einer aux Lage zu lesen:
def storage: Storage[avro.SpecificRecord]
val iter = new StorageIterator[MyAvroDoc]("foo")
iter.storage.getObject[AuxAvroDoc](aux)
Dies funktioniert natürlich nicht, weil storage
Typ-Parameter ein Platzhalter ist, und es gibt keinen Beweis dafür, dass es verwendet werden kann AuxAvroDoc
Ich versuche zu reparieren es so zu lesen:
class StorageIterator2[P, +T <: P](storage: Storage[P])
extends StorageIterator[T](storage)
Dies funktioniert, aber jetzt habe ich zwei Typen params angeben, wenn es zu schaffen, und das saugt :( ich habe versucht, um ihn zu arbeiten, indem ein Verfahren zum Storage
Zugabe von selbst:
trait Storage[-T] {
...
def iterate[R <: T](path: String) =
new StorageIterator2[T, R](this, path)
}
Aber das kompiliert nicht, weil es setzt T
in eine invariante Position :( Und wenn ich P
contravariant mache, dann StorageIterator2[-P, +T <: P]
schlägt fehl, weil es denkt, dass P occurs in covariant position in type P of value T
.
Dieser letzte Fehler verstehe ich nicht. Warum genau kann P
hier nicht kontravariant sein? Wenn diese Position wirklich kovariant ist (warum ist das so?), Warum erlaubt es mir, dort einen invarianten Parameter anzugeben?
Hat schließlich jemand eine Idee, wie ich das umgehen kann? Grundsätzlich ist die Idee zu
storage.iterate[MyAvroDoc]
Sie der Lage sein, ohne dass die obere Grenze wieder zu ergeben, unditerator.storage.getObject[AnotherAvroDoc]
tun, ohne den Speicher zu werfen, die zu beweisen, dass es diese Art von Objekt lesen .
Alle Ideen sind willkommen.
„A Storage [T ] ist ein Objekt, dem Sie Wege geben und Ts ausgeben. " Das ist nicht wahr. 'T' ist NICHT der Typ des Objekts, das' Storage' zurückgibt, sondern ein allgemeiner Supertyp aller _Objekte, die es zurückgeben kann (gewährt, sie sind alle Ts in gewisser Weise, aber das ist eine technische Eigenschaft, keine begriffliche Eigenschaft). Es muss kontravariant sein, da 'Storage [Any]' wie 'Stroage [Foo]' verwendet werden kann. – Dima
"Sie sagen, dass es andersherum sein sollte, dass ein Storage [T] wissen sollte, wie man einen Subtyp von T ausgibt, aber wie würde das funktionieren?" Ich zeigte ein Beispiel davon in der Frage. Wenn zum Beispiel 'T' eine Basisklasse für avro-Strukturen ist, kann' Storage' jedes Avro-Dokument lesen. Es könnte ein "ThriftStruct" sein, um Sparsamkeit zu lesen, oder "Message" für Protobuf, ein "Produkt" für CSV, usw. Es macht keinen Sinn, für jedes Avro-Dokument in der Anwendung eine separate "Storage" -Klasse zu haben was müsste passieren, wenn es kovariant wäre. – Dima
Casting und 'match''ing könnten ein Workaround sein, aber das ist so ein Java-Weg, Dinge zu tun. Diese Art von Hacks zu vermeiden, ist genau der Zweck einer Typenvarianz. Die Idee ist, zur Kompilierzeit deklarieren zu können: "Dieses Objekt kann jedes avro doc und nichts anderes lesen", um Fehler zur Laufzeit nicht zu erzeugen, wenn es auf etwas trifft, zu dem es nicht soll. – Dima