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 {})
}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow