Scala Language
Tipo Varianza
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 {})
}