Scala Language
Typabweichung
Suche…
Kovarianz
Das Symbol +
kennzeichnet einen Typparameter als Kovariante - hier heißt es " Producer
ist Kovariante auf A
":
trait Producer[+A] {
def produce: A
}
Ein kovarianter Parameter kann als "Ausgabe" -Typ betrachtet werden. Die Markierung A
als Kovariante besagt, dass Producer[X] <: Producer[Y]
vorausgesetzt, dass X <: Y
Zum Beispiel ist ein Producer[Cat]
ein gültiger Producer[Animal]
, da alle produzierten Katzen auch gültige Tiere sind.
Ein kovarianter Parameter kann nicht an einer anderen Position (Eingabeposition) angezeigt werden. Das folgende Beispiel wird nicht kompiliert, da wir behaupten, dass Co[Cat] <: Co[Animal]
, aber Co[Cat]
def handle(a: Cat): Unit
die kein Animal
so behandeln kann, wie es von Co[Animal]
verlangt wird!
trait Co[+A] {
def produce: A
def handle(a: A): Unit
}
Eine Möglichkeit, mit dieser Einschränkung umzugehen, besteht in der Verwendung von Typparametern, die durch den kovarianten Typparameter begrenzt werden. Im folgenden Beispiel wissen wir, dass B
ein Supertyp von A
. Wenn Sie also die Option[X] <: Option[Y]
für X <: Y
def getOrElse[B >: X](b: => B): B
, wissen wir, dass die Option[X]
def getOrElse[B >: X](b: => B): B
jeden Supertyp von X
akzeptiert - welche die von Option[Y]
geforderten Supertypen von Y
umfasst:
trait Option[+A] {
def getOrElse[B >: A](b: => B): B
}
Invarianz
Standardmäßig sind alle Typparameter invariant - mit trait A[B]
sagen wir, dass " A
für B
invariant ist". Dies bedeutet, dass wir bei zwei Parametrisierungen A[Cat]
und A[Animal]
keine Sub- / Superklassen-Beziehung zwischen diesen beiden Typen behaupten - es gilt nicht, dass A[Cat] <: A[Animal]
oder A[Cat] >: A[Animal]
unabhängig von der Beziehung zwischen Cat
und Animal
.
Varianzanmerkungen geben uns die Möglichkeit, eine solche Beziehung zu deklarieren, und legen Regeln für die Verwendung von Typparametern fest, damit die Beziehung gültig bleibt.
Verstöße
Das -
Symbol kennzeichnet einen Typparameter als kontravariant - hier sagen wir " Handler
ist kontravariant für A
":
trait Handler[-A] {
def handle(a: A): Unit
}
Ein kontravarianter Parameter kann als "Eingabe" -Typ betrachtet werden. Wenn A
als kontravariant markiert wird, wird behauptet, dass Handler[X] <: Handler[Y]
vorausgesetzt, dass X >: Y
Ein Handler[Animal]
ist beispielsweise ein gültiger Handler[Cat]
, da ein Handler[Animal]
auch mit Katzen umgehen muss.
Ein kontravarianter Typparameter kann nicht an der kovarianten Position (Ausgabe) angezeigt werden. Das folgende Beispiel wird nicht kompiliert, da wir behaupten, dass ein Contra[Animal] <: Contra[Cat]
, ein Contra[Animal]
jedoch def produce: Animal
dem nicht garantiert wird, dass es gemäß Contra[Cat]
Katzen produziert!
trait Contra[-A] {
def handle(a: A): Unit
def produce: A
}
Beachten Sie jedoch: Um die Auflösung zu überlasten, invertiert die Widersprüchlichkeit auch die Spezifität eines Typs auf den Parameter für den kontravarianten Typ. - Handler[Animal]
wird als "spezifischer" als Handler[Cat]
.
Da Methoden für Typparameter nicht überladen werden können, wird dieses Verhalten im Allgemeinen nur beim Auflösen impliziter Argumente problematisch. Im folgenden Beispiel wird ofCat
niemals verwendet, da der Rückgabetyp ofAnimal
spezifischer ist:
implicit def ofAnimal: Handler[Animal] = ???
implicit def ofCat: Handler[Cat] = ???
implicitly[Handler[Cat]].handle(new Cat)
Dieses Verhalten wird derzeit geplant , um in dotty zu ändern , und deshalb (als Beispiel) scala.math.Ordering
von seinem Typ Parameter invariant ist T
. Eine Problemumgehung besteht darin, Ihre Typklasse invariant zu machen und die implizite Definition für den Fall, für den sie auf Unterklassen eines bestimmten Typs angewendet werden soll, zu parametrisieren:
trait Person
object Person {
implicit def ordering[A <: Person]: Ordering[A] = ???
}
Kovarianz einer Sammlung
Da Sammlungen in ihrem Elementtyp * normalerweise kovariant sind, kann eine Sammlung eines Untertyps übergeben werden, an dem ein Supertyp erwartet wird:
trait Animal { def name: String }
case class Dog(name: String) extends Animal
object Animal {
def printAnimalNames(animals: Seq[Animal]) = {
animals.foreach(animal => println(animal.name))
}
}
val myDogs: Seq[Dog] = Seq(Dog("Curly"), Dog("Larry"), Dog("Moe"))
Animal.printAnimalNames(myDogs)
// Curly
// Larry
// Moe
Es mag nicht magisch erscheinen, aber die Tatsache, dass ein Seq[Dog]
von einer Methode akzeptiert wird, die ein Seq[Animal]
erwartet, ist das gesamte Konzept eines höherwertigen Typs (hier: Seq
), der in seinem Typparameter kovariant ist.
*
Ein Gegenbeispiel ist das Set der Standardbibliothek
Kovarianz bei einer unveränderlichen Eigenschaft
Es gibt auch eine Möglichkeit, dass eine einzelne Methode ein kovariantes Argument akzeptiert, anstatt das gesamte Merkmal kovariant zu haben. Dies kann notwendig sein, weil Sie T
in einer kontravarianten Position verwenden möchten, es aber dennoch kovariant sind.
trait LocalVariance[T]{
/// ??? throws a NotImplementedError
def produce: T = ???
// the implicit evidence provided by the compiler confirms that S is a
// subtype of T.
def handle[S](s: S)(implicit evidence: S <:< T) = {
// and we can use the evidence to convert s into t.
val t: T = evidence(s)
???
}
}
trait A {}
trait B extends A {}
object Test {
val lv = new LocalVariance[A] {}
// now we can pass a B instead of an A.
lv.handle(new B {})
}