-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathAboutTypeVariance.scala
233 lines (162 loc) · 7.62 KB
/
AboutTypeVariance.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
package org.functionalkoans.forscala
import support.KoanSuite
import org.scalatest.matchers.ShouldMatchers
class AboutTypeVariance extends KoanSuite with ShouldMatchers {
class Fruit
abstract class Citrus extends Fruit
class Orange extends Citrus
class Tangelo extends Citrus
class Apple extends Fruit
class Banana extends Fruit
koan("""Using type inference the type that you instantiate it will be the val or var reference type""") {
class MyContainer[A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
private[this] var item = a
def get = item
def set(a: A) {
item = a
}
def contents = manifest.runtimeClass.getSimpleName
}
val fruitBasket = new MyContainer(new Orange())
fruitBasket.contents should be("Orange")
}
koan("""You can explicitly declare the type variable of the object during instantiation""") {
class MyContainer[A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
private[this] var item = a
def get = item
def set(a: A) {
item = a
}
def contents = manifest.runtimeClass.getSimpleName
}
val fruitBasket = new MyContainer[Fruit](new Orange())
fruitBasket.contents should be("Fruit")
}
koan("You can coerece your object to a type.") {
class MyContainer[A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
private[this] var item = a
def get = item
def set(a: A) {
item = a
}
def contents = manifest.runtimeClass.getSimpleName
}
val fruitBasket: MyContainer[Fruit] = new MyContainer(new Orange())
fruitBasket.contents should be("Fruit")
}
// That one probably blew your mind. Now if you assign a type to the instantiation,
// that's different to the variable type, you'll have problems. You may want to take time after this
// o compare this koan with the previous koan to compare and contrast. """) {
koan("variable type must match assigned type") {
class MyContainer[A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
private[this] var item = a
def get = item
def set(a: A) {
item = a
}
def contents = manifest.runtimeClass.getSimpleName
}
// Uncomment the following line
// val fruitBasket:MyContainer[Fruit] = new MyContainer[Orange](new Orange())
}
// So, if you want to set a Fruit basket to an orange basket so how do we fix that? You make it covariant using +.
// This will allow you to set the your container to a either a variable with the same type or parent type.
// In other words, you can assign MyContainer[Fruit] or MyContainer[Citrus]."""
koan("covariance lets you specify the container of that type or parent type") {
class MyContainer[+A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
private[this] val item = a
def get = item
def contents = manifest.runtimeClass.getSimpleName
}
val fruitBasket: MyContainer[Fruit] = new MyContainer[Orange](new Orange())
fruitBasket.contents should be("Orange")
}
// The problem with covariance is that you can't mutate, set, or change the object since
// it has to guarantee that what you put in has to be that type. In other words the reference is a fruit basket,
// but we still have to make sure that no other fruit can be placed in our orange basket"""
koan("mutating an object is not allowed with covariance") {
class MyContainer[+A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
private[this] val item = a
def get = item
def contents = manifest.runtimeClass.getSimpleName
}
val fruitBasket: MyContainer[Fruit] = new MyContainer[Orange](new Orange())
fruitBasket.contents should be("Orange")
class NavelOrange extends Orange //Creating a subtype to prove a point
// val navelOrangeBasket: MyContainer[NavelOrange] = new MyContainer[Orange](new Orange()) //Bad!
// val tangeloBasket: MyContainer[Tangelo] = new MyContainer[Orange](new Orange()) //Bad!
}
// Declaring - indicates contravariance variance.
// Using - you can apply any container with a certain type to a container with a superclass of that type.
// This is reverse to covariant. In our example, we can set a citrus basket to
// an orange or tangelo basket. Since an orange or tangelo basket is a citrus basket
koan("contravariance is the opposite of covariance") {
class MyContainer[-A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
private[this] var item = a
def set(a: A) {
item = a
}
def contents = manifest.runtimeClass.getSimpleName
}
val citrusBasket: MyContainer[Citrus] = new MyContainer[Citrus](new Orange)
citrusBasket.contents should be("Citrus")
val orangeBasket: MyContainer[Orange] = new MyContainer[Citrus](new Tangelo)
orangeBasket.contents should be("Citrus")
val tangeloBasket: MyContainer[Tangelo] = new MyContainer[Citrus](new Orange)
tangeloBasket.contents should be("Citrus")
val orangeBasketReally: MyContainer[Orange] = tangeloBasket.asInstanceOf[MyContainer[Orange]]
orangeBasketReally.contents should be("Citrus")
orangeBasketReally.set(new Orange())
}
// Declaring contravariance variance with - also means that the container cannot be accessed with a getter or
// or some other accessor, since that would cause type inconsistency. In our example, you can put an orange
// or a tangelo into a citrus basket. Problem is, if you have a reference to an orange basket,
// and if you believe that you have an orange basket then you shouldn't expect to get a
// tangelo out of it.
koan("A reference to a parent type means you cannot anticipate getting a more specific type") {
class MyContainer[-A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
private[this] var item = a
def set(a: A) {
item = a
}
def contents = manifest.runtimeClass.getSimpleName
}
val citrusBasket: MyContainer[Citrus] = new MyContainer[Citrus](new Orange)
citrusBasket.contents should be("Citrus")
val orangeBasket: MyContainer[Orange] = new MyContainer[Citrus](new Tangelo)
orangeBasket.contents should be("Citrus")
val tangeloBasket: MyContainer[Tangelo] = new MyContainer[Citrus](new Orange)
tangeloBasket.contents should be("Citrus")
}
// Declaring neither -/+, indicates invariance variance. You cannot use a superclass
// variable reference (\"contravariant\" position) or a subclass variable reference (\"covariant\" position)
// of that type. In our example, this means that if you create a citrus basket you can only reference that
// that citrus basket with a citrus variable only.
koan("invariance means you need to specify the type exactly") {
class MyContainer[A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
private[this] var item = a
def set(a: A) {
item = a
}
def get = item
def contents = manifest.runtimeClass.getSimpleName
}
val citrusBasket: MyContainer[Citrus] = new MyContainer[Citrus](new Orange)
citrusBasket.contents should be("Citrus")
}
koan("""Declaring a type as invariant also means that you can both mutate and access elements from an object of generic type""") {
class MyContainer[A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
private[this] var item = a
def set(a: A) {
item = a
}
def get = item
def contents = manifest.runtimeClass.getSimpleName
}
val citrusBasket: MyContainer[Citrus] = new MyContainer[Citrus](new Orange)
citrusBasket.set(new Orange)
citrusBasket.contents should be("Citrus")
citrusBasket.set(new Tangelo)
citrusBasket.contents should be("Citrus")
}
}