Scala Language
Разница типов
Поиск…
ковариации
Символ +
отмечает параметр типа как ковариантный - здесь мы говорим, что « 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 {})
}