Recherche…


Covariance

Le symbole + marque un paramètre de type comme covariant - nous disons ici que "le Producer est covariant sur A ":

trait Producer[+A] {
  def produce: A
}

Un paramètre de type covariant peut être considéré comme un type de "sortie". Marquer A comme covariant affirme que Producer[X] <: Producer[Y] condition que X <: Y Par exemple, un Producer[Cat] est un Producer[Animal] valide, car tous les chats produits sont également des animaux valides.

Un paramètre de type covariant ne peut pas apparaître en position contravariante (entrée). L'exemple suivant ne sera pas compilé car nous affirmons que Co[Cat] <: Co[Animal] , mais Co[Cat] a un def handle(a: Cat): Unit ne pouvant gérer aucun Animal comme requis par Co[Animal] !

trait Co[+A] {
  def produce: A
  def handle(a: A): Unit
}

Une approche pour traiter cette restriction consiste à utiliser des paramètres de type limités par le paramètre de type covariant. Dans l'exemple suivant, nous savons que B est un supertype de A Par conséquent, étant donné l' Option[X] <: Option[Y] pour X <: Y , nous savons que l' Option[X] def getOrElse[B >: X](b: => B): B Option[X] def getOrElse[B >: X](b: => B): B peut accepter n'importe quel supertype de X - qui inclut les supertypes de Y comme requis par l’ Option[Y] :

trait Option[+A] {
  def getOrElse[B >: A](b: => B): B
}

Invariance

Par défaut, tous les paramètres de type sont invariants - étant donné le trait A[B] , nous disons que " A est invariant sur B ". Cela signifie que, compte tenu de deux paramétrisations A[Cat] et A[Animal] , nous n’affirmons pas de relation sub / superclass entre ces deux types - cela ne signifie pas que A[Cat] <: A[Animal] ni que A[Cat] >: A[Animal] quel que soit le lien entre le Cat et l' Animal .

Les annotations de variance nous permettent de déclarer une telle relation et imposent des règles sur l’utilisation des paramètres de type pour que la relation reste valide.

Contravariance

Le symbole - marque un paramètre de type comme contravariant - ici nous disons que " Handler est contravariant sur A ":

trait Handler[-A] {
  def handle(a: A): Unit
}

Un paramètre de type contravariant peut être considéré comme un type "d'entrée". Marquer A comme contravariant affirme que Handler[X] <: Handler[Y] fourni que X >: Y Par exemple, un Handler[Animal] est un Handler[Cat] valide Handler[Cat] , car un Handler[Animal] doit également manipuler des chats.

Un paramètre de type contravariant ne peut pas apparaître en position covariante (sortie). L'exemple suivant ne sera pas compilé car nous affirmons qu'un Contra[Animal] <: Contra[Cat] , mais un Contra[Animal] a un def produce: Animal qui n'est pas garanti pour produire des chats comme requis par Contra[Cat] !

trait Contra[-A] {
   def handle(a: A): Unit
   def produce: A
}

Attention toutefois: à des fins de surcharge de résolution, la contravariance inverse également la spécificité d'un type sur le paramètre de type contravariant - Handler[Animal] est considéré comme "plus spécifique" que Handler[Cat] .

Comme il n'est pas possible de surcharger les méthodes sur les paramètres de type, ce comportement ne devient généralement problématique que lors de la résolution des arguments implicites. Dans l'exemple suivant ofCat ne sera jamais utilisé, car le type de retour ofAnimal est plus spécifique:

implicit def ofAnimal: Handler[Animal] = ???
implicit def ofCat: Handler[Cat] = ???

implicitly[Handler[Cat]].handle(new Cat)

Ce comportement est actuellement prévu pour changer dans dotty , et c'est pourquoi (par exemple) scala.math.Ordering est invariant sur son paramètre de type T Une solution de contournement consiste à rendre l'invariant de votre classe de polices et à paramétrer la définition implicite dans le cas où vous souhaitez l'appliquer à des sous-classes d'un type donné:

trait Person
object Person {
  implicit def ordering[A <: Person]: Ordering[A] = ???
}

Covariance d'une collection

Comme les collections sont généralement covariantes dans leur type d'élément *, une collection d'un sous-type peut être transmise lorsqu'un super type est attendu:

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

Cela peut ne pas sembler magique, mais le fait qu'un Seq[Dog] soit accepté par une méthode qui attend un Seq[Animal] est le concept entier d'un type de type supérieur (ici: Seq ) étant covariant dans son paramètre type.

* Un contre-exemple étant le Set de la bibliothèque standard

Covariance sur un trait invariant

Il y a aussi un moyen d'avoir une seule méthode pour accepter un argument covariant, au lieu d'avoir le trait entier covariant. Cela peut être nécessaire car vous souhaitez utiliser T dans une position contravariante, tout en restant covariant.

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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow