Ricerca…


covarianza

Il simbolo + indica un parametro di tipo come covariante - qui diciamo che " Producer is covariant on A ":

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

Un parametro di tipo covariante può essere pensato come un tipo di "output". Contrassegnare A come covariante afferma che Producer[X] <: Producer[Y] condizione che X <: Y Ad esempio, un Producer[Cat] è un Producer[Animal] valido Producer[Animal] , poiché tutti i gatti prodotti sono anche animali validi.

Un parametro di tipo covariante non può apparire in posizione controvariante (input). L'esempio seguente non verrà compilato poiché stiamo affermando che Co[Cat] <: Co[Animal] , ma Co[Cat] ha def handle(a: Cat): Unit che non può gestire alcun Animal come richiesto da Co[Animal] !

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

Un approccio per gestire questa restrizione consiste nell'usare parametri di tipo delimitati dal parametro di tipo covariante. Nel seguente esempio, sappiamo che B è un supertipo di A Pertanto, data Option[X] <: Option[Y] per X <: Y , sappiamo che l' Option[X] 's ha def getOrElse[B >: X](b: => B): B può accettare qualsiasi supertipo di X - che include i supertipi di Y come richiesto Option[Y] :

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

invarianza

Di default tutti i parametri di tipo sono invarianti - dato il trait A[B] , diciamo che " A è invariante su B ". Ciò significa che date due parametrizzazioni A[Cat] e A[Animal] , non asseriamo nessuna relazione sub / superclasse tra questi due tipi - non ritiene che A[Cat] <: A[Animal] né che A[Cat] >: A[Animal] indipendentemente dal rapporto tra Cat e Animal .

Le annotazioni di varianza ci forniscono un mezzo per dichiarare tale relazione e impongono regole sull'uso dei parametri di tipo in modo che la relazione rimanga valida.

controvarianza

Il simbolo - contrassegna un parametro di tipo come controverso - qui diciamo che "Il Handler è controverso su A ":

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

Un parametro di tipo controvariante può essere pensato come un tipo di "input". Contrassegnare A come controvariante afferma che Handler[X] <: Handler[Y] fornito X >: Y Ad esempio, un Handler[Animal] è un Handler[Cat] valido Handler[Cat] , poiché un Handler[Animal] deve anche gestire i gatti.

Un parametro di tipo controvariante non può apparire nella posizione covariante (uscita). L'esempio seguente non verrà compilato poiché stiamo affermando che Contra[Animal] <: Contra[Cat] , tuttavia un Contra[Animal] ha def produce: Animal che non è garantito per produrre gatti come richiesto da Contra[Cat] !

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

Attenzione però: ai fini della risoluzione dell'overloading, la contravarianza inverte anche in modo controintuitivo la specificità di un tipo sul parametro di tipo controvariante - Handler[Animal] è considerato "più specifico" di Handler[Cat] .

Poiché non è possibile sovraccaricare i metodi sui parametri di tipo, questo comportamento diventa generalmente problematico solo quando si risolvono gli argomenti impliciti. Nell'esempio seguente ofCat non verrà mai utilizzato, poiché il tipo di ritorno ofAnimal è più specifico:

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

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

Questo comportamento è attualmente previsto per cambiare in dotty , ed è perché (ad esempio) scala.math.Ordering è invariante sul suo parametro tipo T Una soluzione alternativa consiste nel rendere invariabile la classe di tipizzazione e digitare la parametrizzazione della definizione implicita nell'evento in cui si desidera che venga applicata alle sottoclassi di un determinato tipo:

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

Covarianza di una collezione

Poiché le raccolte sono tipicamente covarianti nel loro tipo di elemento *, una raccolta di un sottotipo può essere passata dove è previsto un super tipo:

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

Potrebbe non sembrare magico, ma il fatto che un Seq[Dog] sia accettato da un metodo che si aspetta un Seq[Animal] è l'intero concetto di un tipo di tipo superiore (qui: Seq ) che è covariante nel suo parametro tipo.

* Un controesempio è il set della libreria standard

Covarianza su un tratto invariante

C'è anche un modo per avere un singolo metodo per accettare un argomento covariante, invece di avere l'intero tratto covariante. Questo potrebbe essere necessario perché vorresti usare T in una posizione controvariante, ma mantenerlo comunque 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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow