Buscar..


Covarianza

El símbolo + marca un parámetro de tipo como covariante . Aquí decimos que "El Producer es covariante en A ":

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

Un parámetro de tipo covariante puede considerarse como un tipo de "salida". Marcar A como covariante afirma que el Producer[X] <: Producer[Y] siempre que X <: Y Por ejemplo, un Producer[Cat] es un Producer[Animal] válido, ya que todos los gatos producidos también son animales válidos.

Un parámetro de tipo covariante no puede aparecer en posición contravariante (entrada). El siguiente ejemplo no se compilará, ya que estamos afirmando que Co[Cat] <: Co[Animal] , pero Co[Cat] tiene def handle(a: Cat): Unit que no puede manejar ningún Animal como lo requiere Co[Animal] !

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

Un enfoque para lidiar con esta restricción es usar parámetros de tipo limitados por el parámetro de tipo covariante. En el siguiente ejemplo, sabemos que B es un supertipo de A Por lo tanto, dada la Option[X] <: Option[Y] para X <: Y , sabemos que la Option[X] def getOrElse[B >: X](b: => B): B puede aceptar cualquier supertipo de X - que incluye los supertipos de Y como lo requiere la Option[Y] :

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

Invariancia

Por defecto, todos los parámetros de tipo son invariantes. Dado el trait A[B] , decimos que " A es invariante en B ". Esto significa que, dadas las dos parametrizaciones A[Cat] y A[Animal] , no afirmamos que exista una relación de subclase / superclase entre estos dos tipos; no sostiene que A[Cat] <: A[Animal] ni que A[Cat] >: A[Animal] independientemente de la relación entre el Cat y el Animal .

Las anotaciones de varianza nos proporcionan un medio para declarar tal relación e impone reglas sobre el uso de parámetros de tipo para que la relación siga siendo válida.

Contravarianza

El símbolo - marca un parámetro de tipo como contravariante - aquí decimos que "El Handler es contravariante en A ":

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

Un parámetro de tipo contravariante se puede considerar como un tipo de "entrada". Marcar A como contravariante afirma que Handler[X] <: Handler[Y] siempre que X >: Y Por ejemplo, un Handler[Animal] es un Handler[Cat] válido Handler[Cat] , como un Handler[Animal] también debe manejar gatos.

Un parámetro de tipo contravariante no puede aparecer en posición covariante (salida). El siguiente ejemplo no se compilará, ya que estamos afirmando que un Contra[Animal] <: Contra[Cat] , sin embargo, un Contra[Animal] tiene def produce: Animal que no está garantizado que produzca gatos como lo exige Contra[Cat] .

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

Sin embargo, tenga en cuenta que, a los fines de la resolución de sobrecarga, la contravarianza también invierte de forma contraintuitiva la especificidad de un tipo en el parámetro de tipo contravariante: se considera que " Handler[Animal] es" más específico "que Handler[Cat] .

Como no es posible sobrecargar los métodos en los parámetros de tipo, este comportamiento generalmente solo se vuelve problemático al resolver argumentos implícitos. En el siguiente ejemplo, nunca se usará ofCat , ya que el tipo de retorno de ofAnimal es más específico:

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

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

Este comportamiento está actualmente programado para cambiar en punto y es por eso que (como ejemplo) scala.math.Ordering es invariante en su tipo de parámetro T Una solución es hacer que su clase de tipo sea invariante, y parametrizar la definición implícita en el caso de que quiera que se aplique a las subclases de un tipo dado:

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

Covarianza de una colección.

Debido a que las colecciones son típicamente covariantes en su tipo de elemento *, se puede pasar una colección de un subtipo donde se espera un supertipo:

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

Puede que no parezca magia, pero el hecho de que un Seq[Dog] sea ​​aceptado por un método que espera un Seq[Animal] es el concepto completo de un tipo de tipo superior (aquí: Seq ) que es covariante en su parámetro de tipo.

* Un contraejemplo es el conjunto de la biblioteca estándar.

La covarianza en un rasgo invariante

También hay una forma de que un solo método acepte un argumento covariante, en lugar de tener todo el rasgo covariante. Esto puede ser necesario porque le gustaría usar T en una posición contravariante, pero aún así es covariante.

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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow