Skip to content

Commit 640c86c

Browse files
committed
Simplify example and minimize diff
1 parent e6f52d9 commit 640c86c

File tree

1 file changed

+15
-25
lines changed

1 file changed

+15
-25
lines changed

_tour/lower-type-bounds.md

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,66 +16,56 @@ While [upper type bounds](upper-type-bounds.html) limit a type to a subtype of a
1616
Here is an example where this is useful:
1717

1818
```scala mdoc:fail
19-
trait Node[+B] {
20-
def prepend(elem: B): Node[B]
19+
trait List[+B] {
20+
def prepend(elem: B): NonEmptyList[B] = NonEmptyList(elem, this)
2121
}
2222

23-
case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
24-
def prepend(elem: B): ListNode[B] = ListNode(elem, this)
25-
def head: B = h
26-
def tail: Node[B] = t
27-
}
23+
case class NonEmptyList[+B](head: B, tail: List[B]) extends List[B]
2824

29-
case class Nil[+B]() extends Node[B] {
30-
def prepend(elem: B): ListNode[B] = ListNode(elem, this)
31-
}
25+
object Nil extends List[Nothing]
3226
```
3327

34-
This program implements a singly-linked list. `Nil` represents an empty element (i.e. an empty list). `class ListNode` is a node which contains an element of type `B` (`head`) and a reference to the rest of the list (`tail`). The `class Node` and its subtypes are covariant because we have `+B`.
28+
This program implements a singly-linked list. `Nil` represents an empty list with no elements. `class NonEmptyList` is a node which contains an element of type `B` (`head`) and a reference to the rest of the list (`tail`). The `trait List` and its subtypes are covariant because we have `+B`.
3529

3630
However, this program does _not_ compile because the parameter `elem` in `prepend` is of type `B`, which we declared *co*variant. This doesn't work because functions are *contra*variant in their parameter types and *co*variant in their result types.
3731

3832
To fix this, we need to flip the variance of the type of the parameter `elem` in `prepend`. We do this by introducing a new type parameter `U` that has `B` as a lower type bound.
3933

4034
```scala mdoc
41-
trait Node[+B] {
42-
def prepend[U >: B](elem: U): Node[U]
35+
trait List[+B] {
36+
def prepend[U >: B](elem: U): NonEmptyList[U] = NonEmptyList(elem, this)
4337
}
4438

45-
case class ListNode[+B](head: B, tail: Node[B]) extends Node[B] {
46-
def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
47-
}
39+
case class NonEmptyList[+B](head: B, tail: List[B]) extends List[B]
4840

49-
object Nil extends Node[Nothing] {
50-
def prepend[U >: Nothing](elem: U): ListNode[U] = ListNode(elem, this)
51-
}
41+
object Nil extends List[Nothing]
5242
```
5343

54-
We have also simplified `ListNode` to leverage its `case class` fields, and `Nil` to be a singleton object; it is a "node of nothing" because it does not hold an element. The type parameter for `Node` is `B` to suggest we want to store birds at each node.
44+
The type parameter for `List` is `B` to suggest we want to keep lists of birds.
5545

5646
Now we can do the following:
5747
```scala mdoc
5848
trait Bird
5949
case class AfricanSwallow() extends Bird
6050
case class EuropeanSwallow() extends Bird
6151

62-
val africanSwallows: Node[AfricanSwallow] = ListNode[AfricanSwallow](AfricanSwallow(), Nil)
63-
val swallowsFromAntarctica: Node[Bird] = Nil
52+
val africanSwallows: List[AfricanSwallow] = Nil.prepend(AfricanSwallow())
53+
val swallowsFromAntarctica: List[Bird] = Nil
6454

6555
// assign swallows to birds
66-
val birds: Node[Bird] = africanSwallows
56+
val birds: List[Bird] = africanSwallows
6757

6858
// add a swallow to birds
6959
val moreBirds = birds.prepend(EuropeanSwallow())
7060

7161
// add disparate swallows together to get birds
7262
val allBirds = africanSwallows.prepend(EuropeanSwallow())
7363

74-
// but this is a mistake! adding a Node to birds widens the type arg too much. -Xlint will warn!
64+
// but this is a mistake! adding a list of birds widens the type arg too much. -Xlint will warn!
7565
val error = moreBirds.prepend(swallowsFromAntarctica)
7666
```
7767
The covariant type parameter allows `birds` to get the value of `africanSwallows`.
7868

79-
The type bound on the type parameter for `prepend` allows adding different varieties of swallows and getting a wider type: instead of `Node[AfricanSwallow]`, we get a `Node[Bird]`.
69+
The type bound on the type parameter for `prepend` allows adding different varieties of swallows and getting a wider type: instead of `List[AfricanSwallow]`, we get a `List[Bird]`.
8070

8171
The canary in the coal mine is `-Xlint`, which will warn if the type arg is widened too much.

0 commit comments

Comments
 (0)