-
Notifications
You must be signed in to change notification settings - Fork 1k
Rewrote variances tour #741
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,31 +10,142 @@ next-page: upper-type-bounds | |
previous-page: generic-classes | ||
--- | ||
|
||
Scala supports variance annotations of type parameters of [generic classes](generic-classes.html). In contrast to Java 5 (aka. [JDK 1.5](http://java.sun.com/j2se/1.5/)), variance annotations may be added when a class abstraction is defined, whereas in Java 5, variance annotations are given by clients when a class abstraction is used. | ||
Variance is the correlation of subtyping relationships of complex types and the subtyping relationships of their component types. Scala supports variance annotations of type parameters of [generic classes](generic-classes.html), to allow them to be covariant, contravariant, or invariant if no annotations are used. The use of variance in the type system allows us to make intuitive connections between complex types, whereas the lack of variance can restrict the reuse of a class abstraction. | ||
|
||
In the page about [generic classes](generic-classes.html) an example for a mutable stack was given. We explained that the type defined by the class `Stack[T]` is subject to invariant subtyping regarding the type parameter. This can restrict the reuse of the class abstraction. We now derive a functional (i.e. immutable) implementation for stacks which does not have this restriction. Please note that this is an advanced example which combines the use of [polymorphic methods](polymorphic-methods.html), [lower type bounds](lower-type-bounds.html), and covariant type parameter annotations in a non-trivial fashion. Furthermore we make use of [inner classes](inner-classes.html) to chain the stack elements without explicit links. | ||
```tut | ||
class Foo[+A] // A covariant class | ||
class Bar[-A] // A contravariant class | ||
class Baz[A] // An invariant class | ||
``` | ||
|
||
### Covariance | ||
|
||
A type parameter `A` of a generic class can be made covariant by using the annotation `+A`. For some `class List[+A]`, making `A` covariant implies that for two types `A` and `B` where `A` is a subtype of `B`, then `List[A]` is a subtype of `List[B]`. This allows us to make very useful and intuitive subtyping relationships using generics. | ||
|
||
Consider this simple class structure: | ||
|
||
```tut | ||
abstract class Animal { | ||
def name: String | ||
} | ||
case class Cat(name: String) extends Animal | ||
case class Dog(name: String) extends Animal | ||
``` | ||
|
||
Both `Cat` and `Dog` are subtypes of `Animal`. The Scala standard library has a generic immutable `sealed abstract class List[+A]` class, where the type parameter `A` is covariant. This means that a `List[Cat]` is a `List[Animal]` and a `List[Dog]` is also a `List[Animal]`. Intuitively, it makes sense that a list of cats and a list of dogs are each lists of animals, and you should be able to substitute either of them for a `List[Animal]`. | ||
|
||
In the following example, the method `printAnimalNames` will accept a list of animals as an argument and print their names each on a new line. If `List[A]` were not covariant, the last two method calls would not compile, which would severely limit the usefulness of the `printAnimalNames` method. | ||
|
||
```tut | ||
class Stack[+T] { | ||
def push[S >: T](elem: S): Stack[S] = new Stack[S] { | ||
override def top: S = elem | ||
override def pop: Stack[S] = Stack.this | ||
override def toString: String = | ||
elem.toString + " " + Stack.this.toString | ||
object CovarianceTest extends App { | ||
def printAnimalNames(animals: List[Animal]): Unit = { | ||
animals.foreach { animal => | ||
println(animal.name) | ||
} | ||
} | ||
def top: T = sys.error("no element on stack") | ||
def pop: Stack[T] = sys.error("no element on stack") | ||
override def toString: String = "" | ||
|
||
val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom")) | ||
val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex")) | ||
|
||
printAnimalNames(cats) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you put the output in a comment? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since it prints out multiple lines, what might look acceptable? Something like this?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a multiline comment? |
||
// Whiskers | ||
// Tom | ||
|
||
printAnimalNames(dogs) | ||
// Fido | ||
// Rex | ||
} | ||
``` | ||
|
||
### Contravariance | ||
|
||
A type parameter `A` of a generic class can be made contravariant by using the annotation `-A`. This creates a subtyping relationship between the class and its type parameter that is similar, but opposite to what we get with covariance. That is, for some `class Writer[-A]`, making `A` contravariant implies that for two types `A` and `B` where `A` is a subtype of `B`, `Writer[B]` is a subtype of `Writer[A]`. | ||
|
||
Consider the `Cat`, `Dog`, and `Animal` classes defined above for the following example: | ||
|
||
```tut | ||
abstract class Printer[-A] { | ||
def print(value: A): Unit | ||
} | ||
``` | ||
|
||
A `Printer[A]` is a simple class that knows how to print out some type `A`. Let's define some subclasses for specific types: | ||
|
||
```tut | ||
class AnimalPrinter extends Printer[Animal] { | ||
def print(animal: Animal): Unit = | ||
println("The animal's name is: " + animal.name) | ||
} | ||
|
||
object VariancesTest extends App { | ||
var s: Stack[Any] = new Stack().push("hello") | ||
s = s.push(new Object()) | ||
s = s.push(7) | ||
println(s) | ||
class CatPrinter extends Printer[Cat] { | ||
def print(cat: Cat): Unit = | ||
println("The cat's name is: " + cat.name) | ||
} | ||
``` | ||
|
||
The annotation `+T` declares type `T` to be used only in covariant positions. Similarly, `-T` would declare `T` to be used only in contravariant positions. For covariant type parameters we get a covariant subtype relationship regarding this type parameter. For our example this means `Stack[T]` is a subtype of `Stack[S]` if `T` is a subtype of `S`. The opposite holds for type parameters that are tagged with a `-`. | ||
If a `Printer[Cat]` knows how to print any `Cat` to the console, and a `Printer[Animal]` knows how to print any `Animal` to the console, it makes sense that a `Printer[Animal]` would also know how to print any `Cat`. The inverse relationship does not apply, because a `Printer[Cat]` does not know how to print any `Animal` to the console. Therefore, we should be able to substitute a `Printer[Animal]` for a `Printer[Cat]`, if we wish, and making `Printer[A]` contravariant allows us to do exactly that. | ||
|
||
```tut | ||
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 program will be: | ||
|
||
``` | ||
The cat's name is: Boots | ||
The animal's name is: Boots | ||
``` | ||
|
||
### Invariance | ||
|
||
Generic classes in Scala are invariant by default. This means that they are neither covariant nor contravariant. In the context of the following example, `Container` class is invariant. A `Container[Cat]` is _not_ a `Container[Animal]`, nor is the reverse true. | ||
|
||
```tut | ||
class Container[A](value: A) { | ||
private var _value: A = value | ||
def getValue: A = _value | ||
def setValue(value: A): Unit = { | ||
_value = value | ||
} | ||
} | ||
``` | ||
|
||
It may seem like a `Container[Cat]` should naturally also be a `Container[Animal]`, but allowing a mutable generic class to be covariant would not be safe. In this example, it is very important that `Container` is invariant. Supposing `Container` was actually covariant, something like this could happen: | ||
|
||
``` | ||
val catContainer: Container[Cat] = new Container(Cat("Felix")) | ||
val animalContainer: Container[Animal] = catContainer | ||
animalContainer.setValue(Dog("Spot")) | ||
val cat: Cat = catContainer.getValue // Oops, we'd end up with a Dog assigned to a Cat | ||
``` | ||
|
||
Fortunately, the compiler stops us long before we could get this far. | ||
|
||
### Other Examples | ||
|
||
Another example that can help one understand variance is `trait Function1[-T, R]` from the Scala standard library. `Function1` represents a function with one argument, where the first type parameter `T` represents the argument type, and the second type parameter `R` represents the return type. A `Function1` is contravariant over its argument type, and covariant over its return type. For this example we'll use the literal notation `A => B` to represent a `Function1[A, B]`. | ||
|
||
Assume the similar `Cat`, `Dog`, `Animal` inheritance tree used earlier, plus the following: | ||
|
||
```tut | ||
class SmallAnimal | ||
class Mouse extends SmallAnimal | ||
``` | ||
|
||
Suppose we're working with functions that accept types of animals, and return the types of food they eat. If we would like a `Cat => SmallAnimal` (because cats eat small animals), but are given a `Animal => Mouse` instead, our program will still work. Intuitively an `Animal => Mouse` will still accept a `Cat` as an argument, because a `Cat` is an `Animal`, and it returns a `Mouse`, which is also an `SmallAnimal`. Since we can safely and invisibly substitute the former for the latter, we can say `Animal => Mouse` is a subtype of `Cat => SmallAnimal`. | ||
|
||
### Comparison With Other Languages | ||
|
||
For the stack example we would have to use the covariant type parameter `T` in a contravariant position for being able to define method `push`. Since we want covariant subtyping for stacks, we use a trick and abstract over the parameter type of method `push`. We get a polymorphic method in which we use the element type `T` as a lower bound of `push`'s type variable. This has the effect of bringing the variance of `T` in sync with its declaration as a covariant type parameter. Now stacks are covariant, but our solution allows that e.g. it's possible to push a string on an integer stack. The result will be a stack of type `Stack[Any]`; so only if the result is used in a context where we expect an integer stack, we actually detect the error. Otherwise we just get a stack with a more general element type. | ||
Variance is supported in different ways by some languages that are similar to Scala. For example, variance annotations in Scala closely resemble those in C#, where the annotations are added when a class abstraction is defined (declaration-site variance). In Java, however, variance annotations are given by clients when a class abstraction is used (use-site variance). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we start this article with a sentence about what variances are? "Variances are ____". I'm not sure if it's common knowledge. Also, maybe we should just say Java instead of Java 5?