Scala Language
Wpisz wariancję
Szukaj…
Kowariancja
Symbol +
oznacza parametr typu jako kowariant - tutaj mówimy, że „ Producer
jest kowariantem na A
”:
trait Producer[+A] {
def produce: A
}
Parametr typu kowariantnego można traktować jako typ „wyjściowy”. Oznaczenie A
jako kowariant zapewnia, że Producer[X] <: Producer[Y]
pod warunkiem, że X <: Y
Na przykład Producer[Cat]
jest ważnym Producer[Animal]
, ponieważ wszystkie wyprodukowane koty są również ważnymi zwierzętami.
Parametr typu kowariantnego nie może pojawić się w pozycji przeciwstawnej (wejściowej). Poniższy przykład nie zostanie skompilowany, ponieważ twierdzimy, że Co[Cat] <: Co[Animal]
, ale Co[Cat]
ma def handle(a: Cat): Unit
która nie może obsłużyć żadnego Animal
zgodnie z wymaganiami Co[Animal]
!
trait Co[+A] {
def produce: A
def handle(a: A): Unit
}
Jednym podejściem do radzenia sobie z tym ograniczeniem jest użycie parametrów typu ograniczonych przez parametr typu kowariantnego. W poniższym przykładzie wiemy, że B
jest nadtypem A
Dlatego biorąc pod uwagę Option[X] <: Option[Y]
dla X <: Y
, wiemy, że Option[X]
def getOrElse[B >: X](b: => B): B
Option[X]
def getOrElse[B >: X](b: => B): B
może zaakceptować dowolny nadtyp X
- który obejmuje nadtypy Y
zgodnie z wymogami Option[Y]
:
trait Option[+A] {
def getOrElse[B >: A](b: => B): B
}
Niezmienność
Domyślnie wszystkie parametry typu są niezmienne - biorąc pod uwagę trait A[B]
, mówimy, że „ A
jest niezmienny na B
”. Oznacza to, że biorąc pod uwagę dwie parametryzacje A[Cat]
i A[Animal]
, nie dochodzimy do żadnej zależności podklasy / nadklasy między tymi dwoma typami - nie uznaje się, że A[Cat] <: A[Animal]
ani że A[Cat] >: A[Animal]
niezależnie od relacji między Cat
a Animal
.
Adnotacje rozbieżności zapewniają nam sposób na zadeklarowanie takiej relacji i narzucają reguły dotyczące używania parametrów typu, aby relacja była ważna.
Kontrawariancja
Symbol -
oznacza parametr typu jako sprzeczny - tutaj mówimy, że „ Handler
jest przeciwny na A
”:
trait Handler[-A] {
def handle(a: A): Unit
}
Parametr typu przeciwstawnego można traktować jako typ „wejściowy”. Oznaczenie A
jako przeciwstawnego potwierdza, że Handler[X] <: Handler[Y]
pod warunkiem, że X >: Y
Na przykład Handler[Animal]
to prawidłowy Handler[Cat]
, ponieważ Handler[Animal]
musi również obsługiwać koty.
Parametr typu przeciwstawnego nie może pojawić się w pozycji kowariantnej (wyjściowej). Poniższy przykład nie zostanie skompilowany, ponieważ twierdzimy, że Contra[Animal] <: Contra[Cat]
, jednak Contra[Animal]
ma def produce: Animal
które nie gwarantuje, że będzie produkowało koty zgodnie z wymaganiami Contra[Cat]
!
trait Contra[-A] {
def handle(a: A): Unit
def produce: A
}
Uważaj jednak: w celu rozwiązania problemu przeciążenia kontrawariancja odwraca również specyficzność typu na parametr typu przeciwstawnego - Handler[Animal]
jest uważany za „bardziej szczegółowy” niż Handler[Cat]
.
Ponieważ nie jest możliwe przeciążenie metod parametrami typu, takie zachowanie zwykle staje się problematyczne tylko przy rozwiązywaniu niejawnych argumentów. W poniższym przykładzie ofCat
nigdy nie będzie używana, ponieważ typ zwracany przez ofAnimal
jest bardziej szczegółowy:
implicit def ofAnimal: Handler[Animal] = ???
implicit def ofCat: Handler[Cat] = ???
implicitly[Handler[Cat]].handle(new Cat)
To zachowanie ma obecnie zmienić się w kropki , i dlatego (na przykład) scala.math.Ordering
jest niezmienne dla parametru typu T
Jednym z obejść tego problemu jest ustawienie niezmiennika typu czcionki i parametryzacja typu niejawnej definicji w przypadku, gdy ma ona dotyczyć podklas danego typu:
trait Person
object Person {
implicit def ordering[A <: Person]: Ordering[A] = ???
}
Kowariancja kolekcji
Ponieważ kolekcje są zwykle kowariantne w swoim typie elementu *, kolekcja podtypu może zostać przekazana tam, gdzie oczekiwany jest super typ:
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
Może to nie wydawać się magią, ale fakt, że Seq[Dog]
jest akceptowany przez metodę, która oczekuje, że Seq[Animal]
jest całą koncepcją typu wyższego rodzaju (tutaj: Seq
) będącego kowariantem w parametrze typu.
*
Kontrprzykładem jest zestaw standardowej biblioteki
Kowariancja niezmiennej cechy
Istnieje również sposób, aby jedna metoda zaakceptowała argument kowariantny, zamiast posiadania całej cechy kowariantnej. Może to być konieczne, ponieważ chciałbyś użyć T
w pozycji przeciwstawnej, ale nadal masz to kowariant.
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 {})
}