Scala Language
Type Variantie
Zoeken…
covariantie
Het symbool +
markeert een parameter type als covariant - hier zeggen we dat " Producer
covariant is op A
":
trait Producer[+A] {
def produce: A
}
Een parameter van het covariante type kan worden beschouwd als een type "uitvoer". Markering A
als covariant beweert dat Producer[X] <: Producer[Y]
voorwaarde dat X <: Y
Een Producer[Cat]
is bijvoorbeeld een geldige Producer[Animal]
, omdat alle geproduceerde katten ook geldige dieren zijn.
Een parameter van het covariante type kan niet in een contravariante (invoer) positie verschijnen. Het volgende voorbeeld compileert niet, omdat we beweren dat Co[Cat] <: Co[Animal]
, maar Co[Cat]
def handle(a: Cat): Unit
die geen Animal
kan verwerken zoals vereist door Co[Animal]
!
trait Co[+A] {
def produce: A
def handle(a: A): Unit
}
Een benadering om met deze beperking om te gaan, is het gebruik van typeparameters begrensd door de parameter van het covariante type. In het volgende voorbeeld weten we dat B
een supertype van A
. Daarom gezien Option[X] <: Option[Y]
voor X <: Y
, we weten dat Option[X]
def getOrElse[B >: X](b: => B): B
kan elk supertype van X
accepteren - die de supertypen van Y
zoals vereist door Option[Y]
:
trait Option[+A] {
def getOrElse[B >: A](b: => B): B
}
invariantie
Standaard zijn alle typeparameters invariant - gegeven trait A[B]
, zeggen we dat " A
invariant is op B
". Dit betekent dat we, gegeven twee parametriseringen A[Cat]
en A[Animal]
, geen sub / superklasse-relatie tussen deze twee typen beweren - het geldt niet dat A[Cat] <: A[Animal]
noch dat A[Cat] >: A[Animal]
ongeacht de relatie tussen Cat
en Animal
.
Afwijkingsannotaties bieden ons een manier om een dergelijke relatie aan te geven en legt regels op voor het gebruik van typeparameters zodat de relatie geldig blijft.
contravariance
Het -
symbool markeert een typeparameter als contravariant - hier zeggen we dat " Handler
contravariant is op A
":
trait Handler[-A] {
def handle(a: A): Unit
}
Een parameter van het contravariante type kan worden gezien als een "invoer" -type. Markering A
als contravariant beweert dat Handler[X] <: Handler[Y]
voorwaarde dat X >: Y
Een Handler[Animal]
is bijvoorbeeld een geldige Handler[Cat]
, omdat een Handler[Animal]
ook met katten moet omgaan.
Een parameter van het type contravariant kan niet in covariante (uitvoer) positie verschijnen. Het volgende voorbeeld compileert niet omdat we beweren dat een Contra[Animal] <: Contra[Cat]
, maar een Contra[Animal]
def produce: Animal
dat niet gegarandeerd katten kan produceren zoals vereist door Contra[Cat]
!
trait Contra[-A] {
def handle(a: A): Unit
def produce: A
}
Let echter op: met het oog op overbelastingsresolutie keert tegenstrijdigheid ook tegengesteld de specificiteit van een type op de parameter van het contravariante type - Handler[Animal]
wordt beschouwd als "specifieker" dan Handler[Cat]
.
Omdat het niet mogelijk is om methoden op typeparameters te overbelasten, wordt dit gedrag meestal alleen problematisch bij het oplossen van impliciete argumenten. In het volgende voorbeeld ofCat
zal nooit worden gebruikt, omdat het ofAnimal
specifieker is:
implicit def ofAnimal: Handler[Animal] = ???
implicit def ofCat: Handler[Cat] = ???
implicitly[Handler[Cat]].handle(new Cat)
Dit gedrag is momenteel gepland om in puntjes te veranderen , en is daarom (als een voorbeeld) scala.math.Ordering
is invariant op zijn type parameter T
U kunt dit probleem omzeilen door uw typeklasse invariant te maken en de impliciete definitie te parametreren in het geval u deze wilt toepassen op subklassen van een bepaald type:
trait Person
object Person {
implicit def ordering[A <: Person]: Ordering[A] = ???
}
Covariantie van een verzameling
Omdat collecties doorgaans covariant zijn in hun elementtype *, kan een collectie van een subtype worden doorgegeven waar een supertype wordt verwacht:
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
Het lijkt misschien niet magisch, maar het feit dat een Seq[Dog]
wordt geaccepteerd door een methode die een Seq[Animal]
is het hele concept van een hoger soort (hier: Seq
) dat covariant is in zijn typeparameter.
*
Een tegenvoorbeeld is de set van de standaardbibliotheek
Covariantie op een onveranderlijke eigenschap
Er is ook een manier om een enkele methode een covariant argument te laten accepteren, in plaats van de hele eigenschap covariant te hebben. Dit kan nodig zijn omdat u T
in een contravariante positie wilt gebruiken, maar toch covariant wilt hebben.
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 {})
}