Skip to content

Commit 1d643ff

Browse files
authored
Merge pull request #2355 from som-snytt/review/2347
Lower bound in tour includes dangers
2 parents 518e33c + f69fb47 commit 1d643ff

File tree

1 file changed

+33
-28
lines changed

1 file changed

+33
-28
lines changed

_tour/lower-type-bounds.md

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,52 +16,57 @@ 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[+A] {
20+
def prepend(elem: A): NonEmptyList[A] = 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[+A](head: A, tail: List[A]) extends List[A]
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 `A` (`head`) and a reference to the rest of the list (`tail`). The `trait List` and its subtypes are covariant because we have `+A`.
3529

36-
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.
30+
However, this program does _not_ compile because the parameter `elem` in `prepend` is of type `A`, 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

38-
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.
32+
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 `B` that has `A` 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[+A] {
36+
def prepend[B >: A](elem: B): NonEmptyList[B] = NonEmptyList(elem, this)
4337
}
4438

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

51-
case class Nil[+B]() extends Node[B] {
52-
def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
53-
}
41+
object Nil extends List[Nothing]
5442
```
55-
5643
Now we can do the following:
5744
```scala mdoc
5845
trait Bird
5946
case class AfricanSwallow() extends Bird
6047
case class EuropeanSwallow() extends Bird
6148

49+
val africanSwallows: List[AfricanSwallow] = Nil.prepend(AfricanSwallow())
50+
val swallowsFromAntarctica: List[Bird] = Nil
51+
val someBird: Bird = EuropeanSwallow()
52+
53+
// assign swallows to birds
54+
val birds: List[Bird] = africanSwallows
6255

63-
val africanSwallowList = ListNode[AfricanSwallow](AfricanSwallow(), Nil())
64-
val birdList: Node[Bird] = africanSwallowList
65-
birdList.prepend(EuropeanSwallow())
56+
// add some bird to swallows, `B` is `Bird`
57+
val someBirds = africanSwallows.prepend(someBird)
58+
59+
// add a swallow to birds
60+
val moreBirds = birds.prepend(EuropeanSwallow())
61+
62+
// add disparate swallows together, `B` is `Bird` because that is the supertype common to both swallows
63+
val allBirds = africanSwallows.prepend(EuropeanSwallow())
64+
65+
// but this is a mistake! adding a list of birds widens the type arg too much. -Xlint will warn!
66+
val error = moreBirds.prepend(swallowsFromAntarctica) // List[Object]
6667
```
67-
The `Node[Bird]` can be assigned the `africanSwallowList` but then accept `EuropeanSwallow`s.
68+
The covariant type parameter allows `birds` to get the value of `africanSwallows`.
69+
70+
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]`.
71+
72+
Use `-Xlint` to warn if the inferred type arg is widened too much.

0 commit comments

Comments
 (0)