Skip to content

Commit 36b8244

Browse files
authored
add code tab in types-variance.md (#2756)
1 parent 864d1a5 commit 36b8244

File tree

2 files changed

+172
-7
lines changed

2 files changed

+172
-7
lines changed

_overviews/scala3-book/types-variance.md

+90-5
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,41 @@ next-page: types-opaque-types
1111
Type parameter _variance_ controls the subtyping of parameterized types (like classes or traits).
1212

1313
To explain variance, let us assume the following type definitions:
14+
15+
{% tabs types-variance-1 %}
16+
{% tab 'Scala 2 and 3' %}
1417
```scala
1518
trait Item { def productNumber: String }
1619
trait Buyable extends Item { def price: Int }
1720
trait Book extends Buyable { def isbn: String }
21+
1822
```
23+
{% endtab %}
24+
{% endtabs %}
1925

2026
Let us also assume the following parameterized types:
27+
28+
{% tabs types-variance-2 class=tabs-scala-version %}
29+
{% tab 'Scala 2' for=types-variance-2 %}
30+
```scala
31+
// an example of an invariant type
32+
trait Pipeline[T] {
33+
def process(t: T): T
34+
}
35+
36+
// an example of a covariant type
37+
trait Producer[+T] {
38+
def make: T
39+
}
40+
41+
// an example of a contravariant type
42+
trait Consumer[-T] {
43+
def take(t: T): Unit
44+
}
45+
```
46+
{% endtab %}
47+
48+
{% tab 'Scala 3' for=types-variance-2 %}
2149
```scala
2250
// an example of an invariant type
2351
trait Pipeline[T]:
@@ -31,6 +59,9 @@ trait Producer[+T]:
3159
trait Consumer[-T]:
3260
def take(t: T): Unit
3361
```
62+
{% endtab %}
63+
{% endtabs %}
64+
3465
In general there are three modes of variance:
3566

3667
- **invariant**---the default, written like `Pipeline[T]`
@@ -45,6 +76,22 @@ This means that types like `Pipeline[Item]`, `Pipeline[Buyable]`, and `Pipeline[
4576

4677
And rightfully so! Assume the following method that consumes two values of type `Pipeline[Buyable]`, and passes its argument `b` to one of them, based on the price:
4778

79+
{% tabs types-variance-3 class=tabs-scala-version %}
80+
{% tab 'Scala 2' for=types-variance-3 %}
81+
```scala
82+
def oneOf(
83+
p1: Pipeline[Buyable],
84+
p2: Pipeline[Buyable],
85+
b: Buyable
86+
): Buyable = {
87+
val b1 = p1.process(b)
88+
val b2 = p2.process(b)
89+
if (b1.price < b2.price) b1 else b2
90+
}
91+
```
92+
{% endtab %}
93+
94+
{% tab 'Scala 3' for=types-variance-3 %}
4895
```scala
4996
def oneOf(
5097
p1: Pipeline[Buyable],
@@ -55,10 +102,19 @@ def oneOf(
55102
val b2 = p2.process(b)
56103
if b1.price < b2.price then b1 else b2
57104
```
105+
{% endtab %}
106+
{% endtabs %}
107+
58108
Now, recall that we have the following _subtyping relationship_ between our types:
109+
110+
{% tabs types-variance-4 %}
111+
{% tab 'Scala 2 and 3' %}
59112
```scala
60113
Book <: Buyable <: Item
61114
```
115+
{% endtab %}
116+
{% endtabs %}
117+
62118
We cannot pass a `Pipeline[Book]` to the method `oneOf` because in its implementation, we call `p1` and `p2` with a value of type `Buyable`.
63119
A `Pipeline[Book]` expects a `Book`, which can potentially cause a runtime error.
64120

@@ -68,7 +124,6 @@ We cannot pass a `Pipeline[Item]` because calling `process` on it only promises
68124
In fact, type `Pipeline` needs to be invariant since it uses its type parameter `T` _both_ as an argument _and_ as a return type.
69125
For the same reason, some types in the Scala collection library---like `Array` or `Set`---are also _invariant_.
70126

71-
72127
### Covariant Types
73128
In contrast to `Pipeline`, which is invariant, the type `Producer` is marked as **covariant** by prefixing the type parameter with a `+`.
74129
This is valid, since the type parameter is only used in a _return position_.
@@ -78,33 +133,47 @@ And in fact, this is sound. The type of `Producer[Buyable].make` only promises t
78133
As a caller of `make`, we will be happy to also accept a `Book`, which is a subtype of `Buyable`---that is, it is _at least_ a `Buyable`.
79134

80135
This is illustrated by the following example, where the function `makeTwo` expects a `Producer[Buyable]`:
136+
137+
{% tabs types-variance-5 %}
138+
{% tab 'Scala 2 and 3' %}
81139
```scala
82140
def makeTwo(p: Producer[Buyable]): Int =
83141
p.make.price + p.make.price
84142
```
143+
{% endtab %}
144+
{% endtabs %}
145+
85146
It is perfectly fine to pass a producer for books:
86-
```
147+
148+
{% tabs types-variance-6 %}
149+
{% tab 'Scala 2 and 3' %}
150+
```scala
87151
val bookProducer: Producer[Book] = ???
88152
makeTwo(bookProducer)
89153
```
90-
The call to `price` within `makeTwo` is still valid also for books.
154+
{% endtab %}
155+
{% endtabs %}
91156

157+
The call to `price` within `makeTwo` is still valid also for books.
92158

93159
#### Covariant Types for Immutable Containers
94160
You will encounter covariant types a lot when dealing with immutable containers, like those that can be found in the standard library (such as `List`, `Seq`, `Vector`, etc.).
95161

96162
For example, `List` and `Vector` are approximately defined as:
97163

164+
{% tabs types-variance-7 %}
165+
{% tab 'Scala 2 and 3' %}
98166
```scala
99167
class List[+A] ...
100168
class Vector[+A] ...
101169
```
170+
{% endtab %}
171+
{% endtabs %}
102172

103173
This way, you can use a `List[Book]` where a `List[Buyable]` is expected.
104174
This also intuitively makes sense: If you are expecting a collection of things that can be bought, it should be fine to give you a collection of books.
105175
They have an additional ISBN method in our example, but you are free to ignore these additional capabilities.
106176

107-
108177
### Contravariant Types
109178
In contrast to the type `Producer`, which is marked as covariant, the type `Consumer` is marked as **contravariant** by prefixing the type parameter with a `-`.
110179
This is valid, since the type parameter is only used in an _argument position_.
@@ -116,20 +185,34 @@ Remember, for type `Producer`, it was the other way around, and we had `Producer
116185
And in fact, this is sound. The method `Consumer[Item].take` accepts an `Item`.
117186
As a caller of `take`, we can also supply a `Buyable`, which will be happily accepted by the `Consumer[Item]` since `Buyable` is a subtype of `Item`---that is, it is _at least_ an `Item`.
118187

119-
120188
#### Contravariant Types for Consumers
121189
Contravariant types are much less common than covariant types.
122190
As in our example, you can think of them as “consumers.” The most important type that you might come across that is marked contravariant is the one of functions:
123191

192+
{% tabs types-variance-8 class=tabs-scala-version %}
193+
{% tab 'Scala 2' for=types-variance-8 %}
194+
```scala
195+
trait Function[-A, +B] {
196+
def apply(a: A): B
197+
}
198+
```
199+
{% endtab %}
200+
201+
{% tab 'Scala 3' for=types-variance-8 %}
124202
```scala
125203
trait Function[-A, +B]:
126204
def apply(a: A): B
127205
```
206+
{% endtab %}
207+
{% endtabs %}
208+
128209
Its argument type `A` is marked as contravariant `A`---it consumes values of type `A`.
129210
In contrast, its result type `B` is marked as covariant---it produces values of type `B`.
130211

131212
Here are some examples that illustrate the subtyping relationships induced by variance annotations on functions:
132213

214+
{% tabs types-variance-9 %}
215+
{% tab 'Scala 2 and 3' %}
133216
```scala
134217
val f: Function[Buyable, Buyable] = b => b
135218

@@ -139,6 +222,8 @@ val g: Function[Buyable, Item] = f
139222
// OK to provide a Book where a Buyable is expected
140223
val h: Function[Book, Buyable] = f
141224
```
225+
{% endtab %}
226+
{% endtabs %}
142227

143228
## Summary
144229
In this section, we have encountered three different kinds of variance:

_zh-cn/overviews/scala3-book/types-variance.md

+82-2
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,39 @@ permalink: "/zh-cn/scala3/book/:title.html"
1818

1919
为了解释型变,让我们假设以下类型定义:
2020

21+
{% tabs types-variance-1 %}
22+
{% tab 'Scala 2 and 3' %}
2123
```scala
2224
trait Item { def productNumber: String }
2325
trait Buyable extends Item { def price: Int }
2426
trait Book extends Buyable { def isbn: String }
2527
```
28+
{% endtab %}
29+
{% endtabs %}
2630

2731
我们还假设以下参数化类型:
2832

33+
{% tabs types-variance-2 class=tabs-scala-version %}
34+
{% tab 'Scala 2' for=types-variance-2 %}
35+
```scala
36+
// an example of an invariant type
37+
trait Pipeline[T] {
38+
def process(t: T): T
39+
}
40+
41+
// an example of a covariant type
42+
trait Producer[+T] {
43+
def make: T
44+
}
45+
46+
// an example of a contravariant type
47+
trait Consumer[-T] {
48+
def take(t: T): Unit
49+
}
50+
```
51+
{% endtab %}
52+
53+
{% tab 'Scala 3' for=types-variance-2 %}
2954
```scala
3055
// an example of an invariant type
3156
trait Pipeline[T]:
@@ -39,8 +64,10 @@ trait Producer[+T]:
3964
trait Consumer[-T]:
4065
def take(t: T): Unit
4166
```
67+
{% endtab %}
68+
{% endtabs %}
4269

43-
一般来说,方差有三种模式
70+
一般来说,型变有三种模式
4471

4572
- **不变的**---默认值,写成 `Pipeline[T]`
4673
- **协变**---用`+`注释,例如 `Producer[+T]`
@@ -55,6 +82,25 @@ trait Consumer[-T]:
5582

5683
理所当然地!假设以下方法使用两个类型为`Pipeline[Buyable]` 的值,并根据价格将其参数 `b` 传递给其中一个:
5784

85+
{% tabs types-variance-3 class=tabs-scala-version %}
86+
{% tab 'Scala 2' for=types-variance-3 %}
87+
```scala
88+
def oneOf(
89+
p1: Pipeline[Buyable],
90+
p2: Pipeline[Buyable],
91+
b: Buyable
92+
): Buyable = {
93+
val b1 = p1.process(b)
94+
val b2 = p2.process(b)
95+
if (b1.price < b2.price)
96+
b1
97+
else
98+
b2
99+
}
100+
```
101+
{% endtab %}
102+
103+
{% tab 'Scala 3' for=types-variance-3 %}
58104
```scala
59105
def oneOf(
60106
p1: Pipeline[Buyable],
@@ -65,12 +111,18 @@ def oneOf(
65111
val b2 = p2.process(b)
66112
if b1.price < b2.price then b1 else b2
67113
```
114+
{% endtab %}
115+
{% endtabs %}
68116

69117
现在,回想一下,我们的类型之间存在以下_子类型关系_
70118

119+
{% tabs types-variance-4 %}
120+
{% tab 'Scala 2 and 3' %}
71121
```scala
72122
Book <: Buyable <: Item
73123
```
124+
{% endtab %}
125+
{% endtabs %}
74126

75127
我们不能将 `Pipeline[Book]` 传递给 `oneOf` 方法,因为在其实现中,我们调用的 `p1``p2``Buyable` 类型的值。
76128
`Pipeline[Book]` 需要的是 `Book`,这可能会导致运行时错误。
@@ -93,17 +145,25 @@ Book <: Buyable <: Item
93145

94146
以下示例说明了这一点,其中函数 `makeTwo` 需要一个 `Producer[Buyable]`
95147

148+
{% tabs types-variance-5 %}
149+
{% tab 'Scala 2 and 3' %}
96150
```scala
97151
def makeTwo(p: Producer[Buyable]): Int =
98152
p.make.price + p.make.price
99153
```
154+
{% endtab %}
155+
{% endtabs %}
100156

101157
通过书籍制作人是完全可以的:
102158

103-
```
159+
{% tabs types-variance-6 %}
160+
{% tab 'Scala 2 and 3' %}
161+
```scala
104162
val bookProducer: Producer[Book] = ???
105163
makeTwo(bookProducer)
106164
```
165+
{% endtab %}
166+
{% endtabs %}
107167

108168
`makeTwo` 中调用 `price` 对书籍仍然有效。
109169

@@ -113,10 +173,14 @@ makeTwo(bookProducer)
113173

114174
例如,`List``Vector` 大致定义为:
115175

176+
{% tabs types-variance-7 %}
177+
{% tab 'Scala 2 and 3' %}
116178
```scala
117179
class List[+A] ...
118180
class Vector[+A] ...
119181
```
182+
{% endtab %}
183+
{% endtabs %}
120184

121185
这样,您可以在需要 `List[Buyable]` 的地方使用 `List[Book]`
122186
这在直觉上也是有道理的:如果您期望收藏可以购买的东西,那么给您收藏书籍应该没问题。
@@ -139,16 +203,30 @@ class Vector[+A] ...
139203
逆变类型比协变类型少得多。
140204
在我们的示例中,您可以将它们视为“消费者”。你可能来的最重要的类型标记为逆变的 cross 是函数之一:
141205

206+
{% tabs types-variance-8 class=tabs-scala-version %}
207+
{% tab 'Scala 2' for=types-variance-8 %}
208+
```scala
209+
trait Function[-A, +B] {
210+
def apply(a: A): B
211+
}
212+
```
213+
{% endtab %}
214+
215+
{% tab 'Scala 3' for=types-variance-8 %}
142216
```scala
143217
trait Function[-A, +B]:
144218
def apply(a: A): B
145219
```
220+
{% endtab %}
221+
{% endtabs %}
146222

147223
它的参数类型 `A` 被标记为逆变的 `A` ——它消费 `A` 类型的值。
148224
相反,它的结果类型 `B` 被标记为协变——它产生 `B` 类型的值。
149225

150226
以下是一些示例,这些示例说明了由函数上可变注释引起的子类型关系:
151227

228+
{% tabs types-variance-9 %}
229+
{% tab 'Scala 2 and 3' %}
152230
```scala
153231
val f: Function[Buyable, Buyable] = b => b
154232

@@ -158,6 +236,8 @@ val g: Function[Buyable, Item] = f
158236
// OK to provide a Book where a Buyable is expected
159237
val h: Function[Book, Buyable] = f
160238
```
239+
{% endtab %}
240+
{% endtabs %}
161241

162242
## 概括
163243

0 commit comments

Comments
 (0)