You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Variance lets you control how type parameters behave with regards to subtyping. 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.
14
14
15
+
{% tabs variances_1 %}
16
+
{% tab 'Scala 2 and 3' for=variances_1 %}
15
17
```scala mdoc
16
18
classFoo[+A] // A covariant class
17
19
classBar[-A] // A contravariant class
18
20
classBaz[A] // An invariant class
19
21
```
22
+
{% endtab %}
23
+
{% endtabs %}
20
24
21
25
### Invariance
22
26
23
27
By default, type parameters in Scala are invariant: subtyping relationships between the type parameters aren't reflected in the parameterized type. To explore why this works the way it does, we look at a simple parameterized type, the mutable box.
24
28
29
+
{% tabs invariance_1 %}
30
+
{% tab 'Scala 2 and 3' for=invariance_1 %}
25
31
```scala mdoc
26
32
classBox[A](varcontent:A)
27
33
```
34
+
{% endtab %}
35
+
{% endtabs %}
28
36
29
37
We're going to be putting values of type `Animal` in it. This type is defined as follows:
30
38
39
+
{% tabs invariance_2 class=tabs-scala-version %}
40
+
{% tab 'Scala 2' for=invariance_2 %}
31
41
```scala mdoc
32
42
abstractclassAnimal {
33
43
defname:String
34
44
}
35
45
caseclassCat(name: String) extendsAnimal
36
46
caseclassDog(name: String) extendsAnimal
37
47
```
48
+
{% endtab %}
49
+
{% tab 'Scala 3' for=invariance_2 %}
50
+
```scala
51
+
abstractclassAnimal:
52
+
defname:String
53
+
54
+
caseclassCat(name: String) extendsAnimal
55
+
caseclassDog(name: String) extendsAnimal
56
+
```
57
+
{% endtab %}
58
+
{% endtabs %}
38
59
39
60
We can say that `Cat` is a subtype of `Animal`, and that `Dog` is also a subtype of `Animal`. That means that the following is well-typed:
40
61
62
+
{% tabs invariance_3 %}
63
+
{% tab 'Scala 2 and 3' for=invariance_3 %}
41
64
```scala mdoc
42
-
valmyAnimal:Animal=Cat("Felix")
65
+
valmyAnimal:Animal=Cat("Felix")
43
66
```
67
+
{% endtab %}
68
+
{% endtabs %}
44
69
45
70
What about boxes? Is `Box[Cat]` a subtype of `Box[Animal]`, like `Cat` is a subtype of `Animal`? At first sight, it looks like that may be plausible, but if we try to do that, the compiler will tell us we have an error:
46
71
72
+
{% tabs invariance_4 class=tabs-scala-version %}
73
+
{% tab 'Scala 2' for=invariance_4 %}
74
+
```scala mdoc:fail
75
+
valmyCatBox:Box[Cat] =newBox[Cat](Cat("Felix"))
76
+
valmyAnimalBox:Box[Animal] = myCatBox // this doesn't compile
77
+
valmyAnimal:Animal= myAnimalBox.content
78
+
```
79
+
{% endtab %}
80
+
{% tab 'Scala 3' for=invariance_4 %}
47
81
```scala
48
-
valmyCatBox:Box[Cat] =newBox[Cat](Cat("Felix"))
49
-
valmyAnimalBox:Box[Animal] = myCatBox // this doesn't compile
50
-
valmyAnimal:Animal= myAnimalBox.content
82
+
valmyCatBox:Box[Cat] =Box[Cat](Cat("Felix"))
83
+
valmyAnimalBox:Box[Animal] = myCatBox // this doesn't compile
84
+
valmyAnimal:Animal= myAnimalBox.content
51
85
```
86
+
{% endtab %}
87
+
{% endtabs %}
52
88
53
89
Why could this be a problem? We can get the cat from the box, and it's still an Animal, isn't it? Well, yes. But that's not all we can do. We can also replace the cat in the box with a different animal
54
90
91
+
{% tabs invariance_5 %}
92
+
{% tab 'Scala 2 and 3' for=invariance_5 %}
55
93
```scala
56
94
myAnimalBox.content =Dog("Fido")
57
95
```
96
+
{% endtab %}
97
+
{% endtabs %}
58
98
59
99
There now is a Dog in the Animal box. That's all fine, you can put Dogs in Animal boxes, because Dogs are Animals. But our Animal Box is a Cat Box! You can't put a Dog in a Cat box. If we could, and then try to get the cat from our Cat Box, it would turn out to be a dog, breaking type soundness.
60
100
101
+
{% tabs invariance_6 %}
102
+
{% tab 'Scala 2 and 3' for=invariance_6 %}
61
103
```scala
62
104
valmyCat:Cat= myCatBox.content //myCat would be Fido the dog!
63
105
```
106
+
{% endtab %}
107
+
{% endtabs %}
64
108
65
109
From this, we have to conclude that `Box[Cat]` and `Box[Animal]` can't have a subtyping relationship, even though `Cat` and `Animal` do.
66
110
@@ -70,18 +114,31 @@ The problem we ran in to above, is that because we could put a Dog in an Animal
70
114
71
115
But what if we couldn't put a Dog in the box? Then we could just get our Cat back out and that's not a problem, so than it could follow the subtyping relationship. It turns out, that's indeed something we can do.
valanimalBox:ImmutableBox[Animal] = catbox // now this compiles
77
130
```
131
+
{% endtab %}
132
+
{% endtabs %}
78
133
79
134
We say that `ImmutableBox` is *covariant* in `A`, and this is indicated by the `+` before the `A`.
80
135
81
136
More formally, that gives us the following relationship: given some `class Cov[+T]`, then if `A` is a subtype of `B`, `Cov[A]` is a subtype of `Cov[B]`. This allows us to make very useful and intuitive subtyping relationships using generics.
82
137
83
138
In the following less contrived 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.
84
139
140
+
{% tabs covariance_2 %}
141
+
{% tab 'Scala 2 and 3' for=covariance_2 %}
85
142
```scala mdoc
86
143
defprintAnimalNames(animals: List[Animal]):Unit=
87
144
animals.foreach {
@@ -97,22 +154,40 @@ printAnimalNames(cats)
97
154
// prints: Fido, Rex
98
155
printAnimalNames(dogs)
99
156
```
157
+
{% endtab %}
158
+
{% endtabs %}
100
159
101
160
### Contravariance
102
161
103
162
We've seen we can accomplish covariance by making sure that we can't put something in the covariant type, but only get something out. What if we had the opposite, something you can put something in, but can't take out? This situation arises if we have something like a serializer, that takes values of type A, and converts them to a serialized format.
We say that `Serializer` is *contravariant* in `A`, and this is indicated by the `-` before the `A`. A more general serializer is a subtype of a more specific serializer.
0 commit comments