Scala Language
Typ Varians
Sök…
Covariance
Symbolen +
markerar en typparameter som covariant - här säger vi att " Producer
är covariant på A
":
trait Producer[+A] {
def produce: A
}
En parametrar av kovariantyp kan betraktas som en "output" -typ. Markering A
som samvariant hävdar att Producer[X] <: Producer[Y]
förutsatt att X <: Y
Till exempel är en Producer[Cat]
en giltig Producer[Animal]
, eftersom alla producerade katter också är giltiga djur.
En parameter av kovariantyp kan inte visas i kontrastläge (ingång). Följande exempel kommer inte att sammanställas eftersom vi hävdar att Co[Cat] <: Co[Animal]
, men Co[Cat]
har def handle(a: Cat): Unit
som inte kan hantera något Animal
som krävs av Co[Animal]
!
trait Co[+A] {
def produce: A
def handle(a: A): Unit
}
Ett sätt att hantera denna begränsning är att använda typparametrar avgränsade av parametern covariant. I följande exempel vet vi att B
är en supertyp av A
Därför ges Option[X] <: Option[Y]
för X <: Y
, vi vet att Option[X]
: s def getOrElse[B >: X](b: => B): B
kan acceptera någon supertyp av X
- som inkluderar supertyperna till Y
enligt krav Option[Y]
:
trait Option[+A] {
def getOrElse[B >: A](b: => B): B
}
Invariance
Som standard är alla typparametrar invariant - givet trait A[B]
, vi säger att " A
är invariant på B
". Detta innebär att med tanke på två parametriseringar A[Cat]
och A[Animal]
, hävdar vi inget sub / superklass-förhållande mellan dessa två typer - det gäller inte att A[Cat] <: A[Animal]
eller att A[Cat] >: A[Animal]
oavsett förhållandet mellan Cat
och Animal
.
Variantanteckningar ger oss ett sätt att förklara ett sådant förhållande och inför regler för användningen av typparametrar så att förhållandet förblir giltigt.
Contravariance
Symbolen -
markerar en typparameter som contravariant - här säger vi att " Handler
contravariant on A
":
trait Handler[-A] {
def handle(a: A): Unit
}
En parameter för kontrastvariant kan betraktas som en "input" -typ. Att markera A
som kontroversiellt hävdar att Handler[X] <: Handler[Y]
förutsatt att X >: Y
Till exempel är en Handler[Animal]
en giltig Handler[Cat]
, eftersom Handler[Animal]
måste hantera katter.
En parameter med kontrastvariant kan inte visas i kovariant (utgång) position. Följande exempel kommer inte att sammanställas eftersom vi hävdar att en Contra[Animal] <: Contra[Cat]
, men ett Contra[Animal]
har def produce: Animal
som inte garanteras att producera katter som krävs av Contra[Cat]
!
trait Contra[-A] {
def handle(a: A): Unit
def produce: A
}
Akta dig emellertid: i syfte att överbelasta upplösningen inverterar contravarians också intuitivt specificiteten för en typ i parametern för kontravarianttyp - Handler[Animal]
anses vara "mer specifikt" än Handler[Cat]
.
Eftersom det inte är möjligt att överbelasta metoder på typparametrar blir detta beteende generellt bara problematiskt när man löser implicita argument. I följande exempel kommer ofCat
aldrig att användas, eftersom ofAnimal
är mer specifik:
implicit def ofAnimal: Handler[Animal] = ???
implicit def ofCat: Handler[Cat] = ???
implicitly[Handler[Cat]].handle(new Cat)
Detta beteende är för närvarande planerat för att ändras i prickigt , och är därför (som exempel) scala.math.Ordering
är invariant på sin typparameter T
En lösning är att göra din typkategori invariant och typ-parametrera den implicita definitionen om du vill att den ska tillämpas på underklasser av en viss typ:
trait Person
object Person {
implicit def ordering[A <: Person]: Ordering[A] = ???
}
Covariance i en samling
Eftersom samlingar vanligtvis är kovarianta i sin elementtyp *, kan en samling av en subtyp passeras där en supertyp förväntas:
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
Det kanske inte verkar som magi, men det faktum att en Seq[Dog]
accepteras av en metod som förväntar sig att en Seq[Animal]
är hela konceptet med en högre typ (här: Seq
) som är kovariant i sin typparameter.
*
Ett motexempel är standardbibliotekets uppsättning
Covariance på en invariant egenskap
Det finns också ett sätt att låta en enda metod acceptera ett samvariativt argument, istället för att ha hela egenskaperna. Det kan vara nödvändigt eftersom du vill använda T
i en kontrastläge, men ändå har det samvariativt.
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 {})
}