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 {})
}