Skip to content

Commit c8a8905

Browse files
committed
Rewrote lower type bounds section of tour
1 parent 1a5aabb commit c8a8905

File tree

1 file changed

+33
-29
lines changed

1 file changed

+33
-29
lines changed
+33-29
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
---
1+
---
22
layout: tutorial
33
title: Lower Type Bounds
44

@@ -9,50 +9,54 @@ categories: tour
99
num: 21
1010
next-page: inner-classes
1111
previous-page: upper-type-bounds
12+
prerequisite-knowledge: upper-type-bounds, generics, variance
1213
---
1314

14-
While [upper type bounds](upper-type-bounds.html) limit a type to a subtype of another type, *lower type bounds* declare a type to be a supertype of another type. The term `T >: A` expresses that the type parameter `T` or the abstract type `T` refer to a supertype of type `A`.
15+
While [upper type bounds](upper-type-bounds.html) limit a type to a subtype of another type, *lower type bounds* declare a type to be a supertype of another type. The term `T >: A` expresses that the type parameter `T` or the abstract type `T` refer to a supertype of type `A`. In most cases, `A` will be the type parameter of the class and `T` will be the type parameter of a method.
1516

1617
Here is an example where this is useful:
1718

18-
```tut
19-
case class ListNode[T](h: T, t: ListNode[T]) {
20-
def head: T = h
21-
def tail: ListNode[T] = t
22-
def prepend(elem: T): ListNode[T] =
23-
ListNode(elem, this)
19+
```tut:fail
20+
trait Node[+T] {
21+
def prepend(elem: T)
2422
}
25-
```
2623
27-
The program above implements a linked list with a prepend operation. Unfortunately, this type is invariant in the type parameter of class `ListNode`; i.e. `ListNode[String]` is not a subtype of `ListNode[Any]`. With the help of [variance annotations](variances.html) we can express such a subtype semantics:
24+
case class Nil[+T]() extends Node[T] {
25+
def prepend(elem: T) = ListNode[T](elem, this)
26+
}
2827
28+
case class ListNode[+T](h: T, t: Node[T]) extends Node[T] {
29+
def prepend(elem: T) = ListNode[T](elem, this)
30+
def head: T = h
31+
def tail = t
32+
}
2933
```
30-
case class ListNode[+T](h: T, t: ListNode[T]) { ... }
31-
```
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 `T` (`head`) and a reference to the rest of the list (`tail`). The `class Node` and its subtypes are covariant because we have `+T`.
3235

33-
Unfortunately, this program does not compile, because a covariance annotation is only possible if the type variable is used only in covariant positions. Since type variable `T` appears as a parameter type of method `prepend`, this rule is broken. With the help of a *lower type bound*, though, we can implement a prepend method where `T` only appears in covariant positions.
36+
However, this program does _not_ compile because the parameter `elem` in `prepend` is of type `T`, 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.
3437

35-
Here is the corresponding code:
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 `T` as a lower type bound.
3639

3740
```tut
38-
case class ListNode[+T](h: T, t: ListNode[T]) {
39-
def head: T = h
40-
def tail: ListNode[T] = t
41-
def prepend[U >: T](elem: U): ListNode[U] =
42-
ListNode(elem, this)
41+
trait Node[+T] {
42+
def prepend[U >: T](elem: U)
4343
}
44-
```
4544
46-
_Note:_ the new `prepend` method has a slightly less restrictive type. It allows, for instance, to prepend an object of a supertype to an existing list. The resulting list will be a list of this supertype.
47-
48-
Here is some code which illustrates this:
45+
case class Nil[+T]() extends Node[T] {
46+
def prepend[U >: T](elem: U) = ListNode[U](elem, this)
47+
}
4948
50-
```tut
51-
object LowerBoundTest extends App {
52-
val empty: ListNode[Null] = ListNode(null, null)
53-
val strList: ListNode[String] = empty.prepend("hello")
54-
.prepend("world")
55-
val anyList: ListNode[Any] = strList.prepend(12345)
49+
case class ListNode[+T](h: T, t: Node[T]) extends Node[T] {
50+
def prepend[U >: T](elem: U) = ListNode[U](elem, this)
51+
def head: T = h
52+
def tail = t
5653
}
5754
```
5855

56+
Now we can do the following:
57+
```tut
58+
val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil())
59+
val mammalList: Node[Mammal] = africanSwallowList
60+
mammalList.prepend(EuropeanSwallow)
61+
```
62+
The `Node[Mammal]` can be assigned the `africanSwallowList` but then accept `EuropeanSwallow`s.

0 commit comments

Comments
 (0)