Поиск…


ковариации

Символ + отмечает параметр типа как ковариантный - здесь мы говорим, что « Producer ковариантен на A »:

trait Producer[+A] {
  def produce: A
}

Параметр ковариантного типа можно рассматривать как «выходной» тип. Маркировка A как ковариантная утверждает, что Producer[X] <: Producer[Y] при условии, что X <: Y Например, Producer[Cat] является действительным Producer[Animal] , так как все произведенные кошки также являются действительными животными.

Параметр ковариантного типа не может отображаться в контравариантном (входном) положении. Следующий пример не будет компилироваться, поскольку мы утверждаем, что Co[Cat] <: Co[Animal] , но Co[Cat] имеет def handle(a: Cat): Unit который не может обрабатывать любое Animal по требованию Co[Animal] !

trait Co[+A] {
  def produce: A
  def handle(a: A): Unit
}

Один из подходов к решению этого ограничения заключается в использовании параметров типа, ограниченных параметром ковариантного типа. В следующем примере мы знаем, что B является супертипом A Поэтому данный параметр Option[X] <: Option[Y] для X <: Y , мы знаем, что Option[X] def getOrElse[B >: X](b: => B): B может принимать любой супертип X - который включает в себя супертипы Y как требуется Option[Y] :

trait Option[+A] {
  def getOrElse[B >: A](b: => B): B
}

неизменность

По умолчанию все параметры типа инвариантны - данный trait A[B] , мы говорим, что « A инвариантно на B ». Это означает, что, учитывая две параметризации A[Cat] и A[Animal] , мы не утверждаем никакого отношения sub / superclass между этими двумя типами - он не считает, что A[Cat] <: A[Animal] и A[Cat] >: A[Animal] независимо от отношения между Cat и Animal .

Аннотации вариации предоставляют нам способ объявления таких отношений и налагают правила использования параметров типа, чтобы отношения оставались действительными.

контрвариация

Символ - обозначает параметр типа как контравариантный - здесь мы говорим, что « Handler контравариантен на A »:

trait Handler[-A] {
  def handle(a: A): Unit
}

Параметр контравариантного типа можно рассматривать как «входной» тип. Маркировка A как контравариантная утверждает, что Handler[X] <: Handler[Y] при условии, что X >: Y Например, Handler[Animal] является допустимым Handler[Cat] , поскольку Handler[Animal] также должен обрабатывать кошек.

Параметр контравариантного типа не может появляться в ковариантной (выходной) позиции. Следующий пример не будет компилироваться, поскольку мы утверждаем, что Contra[Animal] <: Contra[Cat] , однако Contra[Animal] имеет def produce: Animal которому не гарантируется создание кошек по требованию Contra[Cat] !

trait Contra[-A] {
   def handle(a: A): Unit
   def produce: A
}

Однако опасайтесь: для целей перегрузки разрешения контравариантность также интуитивно инвертирует специфичность типа на контравариантном параметре типа. Handler[Animal] считается «более конкретным», чем Handler[Cat] .

Поскольку невозможно перегрузить методы для параметров типа, это поведение обычно становится проблематичным при разрешении неявных аргументов. В следующем ofCat никогда не будет использоваться, поскольку тип возвращаемого значения ofAnimal более специфичен:

implicit def ofAnimal: Handler[Animal] = ???
implicit def ofCat: Handler[Cat] = ???

implicitly[Handler[Cat]].handle(new Cat)

В настоящее время это поведение изменено в точках , и именно поэтому (в качестве примера) scala.math.Ordering является инвариантным по его параметру типа T Один из способов - сделать ваш инвариант typeclass и ввести параметризацию неявного определения в случае, если вы хотите, чтобы оно применимо к подклассам заданного типа:

trait Person
object Person {
  implicit def ordering[A <: Person]: Ordering[A] = ???
}

Ковариация коллекции

Поскольку коллекции, как правило, ковариантны по типу элемента *, может быть передан набор подтипов, где ожидается супер-тип:

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

Это может показаться не волшебством, но тот факт, что Seq[Dog] принят методом, который ожидает Seq[Animal] - это все понятие более высокого типа (здесь: Seq ), являющееся ковариантным в своем параметре типа.

* Контрпример, являющийся набором стандартной библиотеки

Ковариация по инвариантной черты

Существует также способ, чтобы один метод принимал ковариантный аргумент, вместо того чтобы иметь ковариант целиком. Это может быть необходимо, потому что вы хотели бы использовать T в контравариантном положении, но все равно иметь ковариантность.

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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow