Type variable is the correlation subtyping of subtype relations thereto complex type component type. Scala supports generic class type parameters of type variable annotation, allowing them to be covariant inverter or without the use of annotations is constant. In the type using variable type system allows us to establish a visual connection between the complex type, and variations will be deficient abstract restricted reuse.
class Foo[+A] // A covariant class
class Bar[-A] // A contravariant class
class Baz[A] // An invariant class
Covariant
Use annotations +A
can make a generic class type parameter A
becomes covariant. For some classes class List[+A]
, so A
become covariant means that for both types A
and B
, if A
a B
sub-type, then List[A]
that is List[B]
a sub-type. This allows us to use generics to create a very useful and intuitive sub-type relationship.
Consider the following simple class structure:
abstract class Animal {
def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
Type Cat
and Dog
all Animal
subtypes. The library has a common Scala immutable classes sealed abstract class List[+A]
, wherein the parameter type A
is covariant. This means that List[Cat]
Shi List[Animal]
, List[Dog]
also List[Animal]
. Intuitively, a list of cat and dog list is a list of animals is reasonable, you should be able to replace any of them with List[Animal]
.
In the following example, the method of printAnimalNames
the animals receiving the list as a parameter and prints out the progressive their names. If List[A]
not covariant, the last two method calls will not be compiled, which will severely limit printAnimalNames
the applicability of the method.
object CovarianceTest extends App {
def printAnimalNames(animals: List[Animal]): Unit = {
animals.foreach { animal =>
println(animal.name)
}
}
val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom"))
val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))
printAnimalNames(cats)
// Whiskers
// Tom
printAnimalNames(dogs)
// Fido
// Rex
}
Inverter
By using annotations -A
, you can make a generic class type parameter A
becomes inverter. And covariant Similarly, it will create a subtype relationship between the class and type of parameters, but the opposite effect and covariant. That is, for a class class Writer[-A]
, the A
inverter means that for both types A
and B
, if A
a B
sub-type, Writer[B]
is Writer[A]
a sub-type.
Consider the use of the following classes defined above embodiment Cat
, Dog
and Animal
:
abstract class Printer[-A] {
def print(value: A): Unit
}
Here Printer[A]
is a simple class that is used to print out a certain type A
. Let us define some specific sub-categories:
class AnimalPrinter extends Printer[Animal] {
def print(animal: Animal): Unit =
println("The animal's name is: " + animal.name)
}
class CatPrinter extends Printer[Cat] {
def print(cat: Cat): Unit =
println("The cat's name is: " + cat.name)
}
If you Printer[Cat]
know how to print out any of the console Cat
, and Printer[Animal]
know how to print out any of the console Animal
, you Printer[Animal]
should also know how to print out Cat
is reasonable. Inverse relationship does not apply, because Printer[Cat]
do not know how to print out any of the console Animal
. So, if we like, we should be able to use Printer[Animal]
the replacement Printer[Cat]
, the Printer[A]
inverter allows us to do this.
object ContravarianceTest extends App {
val myCat: Cat = Cat("Boots")
def printMyCat(printer: Printer[Cat]): Unit = {
printer.print(myCat)
}
val catPrinter: Printer[Cat] = new CatPrinter
val animalPrinter: Printer[Animal] = new AnimalPrinter
printMyCat(catPrinter)
printMyCat(animalPrinter)
}
The output of this procedure are as follows:
The cat's name is: Boots
The animal's name is: Boots
constant
By default, Scala in the generic class is unchanged. This means that they are neither covariant nor contravariant. In the following example, the class Container
is constant. Container[Cat]
NotContainer[Animal]
vice versa.
class Container[A](value: A) {
private var _value: A = value
def getValue: A = _value
def setValue(value: A): Unit = {
_value = value
}
}
May seem a Container[Cat]
natural and should be a Container[Animal]
, but allows a variable to be covariant generic class is not safe. In this example, the Container
constant is very important. Suppose Container
actually covariant, the following may occur:
val catContainer: Container[Cat] = new Container(Cat("Felix"))
val animalContainer: Container[Animal] = catContainer
animalContainer.setValue(Dog("Spot"))
val cat: Cat = catContainer.getValue // 糟糕,我们最终会将一只狗作为值分配给一只猫
Fortunately, the compiler will stop us before that.
Other examples
Another type of change can help to understand example is the Scala standard library trait Function1[-T, +R]
. Function1
It represents a function having a parameter, wherein the first type parameter T
indicates the parameter type, the second type parameter R
indicates the return type. Function1
In which the parameter is an inverter type, and in the return type is covariant. For this example, we will use text symbols A => B
to represent Function1[A, B]
.
Similar assumptions used previously Cat
, Dog
, Animal
inheritance, plus the following:
abstract class SmallAnimal extends Animal
case class Mouse(name: String) extends SmallAnimal
Suppose we are dealing with animals that received the type of function, and return to their type of food. If we want a Cat => SmallAnimal
(because the cat eat small animals), but give it a Animal => Mouse
, our program will still work. Intuitively, a Animal => Mouse
function will still accept a Cat
as an argument, because Cat
that is a Animal
, and the function returns a Mouse
also a SmallAnimal
. Since we can safely implicitly with the former instead of the latter, we can say Animal => Mouse
that Cat => SmallAnimal
subtype.
Comparison with other languages
Scala and some similar language in different ways supportive change. For example, in the type variable Scala Notes and it is very similar to C #, when defining an Abstract addition type variable annotations (dot type variable declaration). In Java, however, it is used when an Abstract (using variable dot type), will be given annotation type variable.