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


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow