Skip to content

Commit d6c5740

Browse files
authored
Merge pull request #760 from travissarles/lower-type-bounds
Rewrote lower type bounds section of tour
2 parents 81d6978 + 57e659f commit d6c5740

File tree

1 file changed

+38
-29
lines changed

1 file changed

+38
-29
lines changed
Lines changed: 38 additions & 29 deletions
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,59 @@ 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 `B >: A` expresses that the type parameter `B` or the abstract type `B` refer to a supertype of type `A`. In most cases, `A` will be the type parameter of the class and `B` 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[+B] {
21+
def prepend(elem: B): Unit
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 ListNode[+B](h: B, t: Node[B]) extends Node[B] {
25+
def prepend(elem: B) = ListNode[B](elem, this)
26+
def head: B = h
27+
def tail = t
28+
}
2829
30+
case class Nil[+B]() extends Node[B] {
31+
def prepend(elem: B) = ListNode[B](elem, this)
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 `B` (`head`) and a reference to the rest of the list (`tail`). The `class Node` and its subtypes are covariant because we have `+B`.
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 `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.
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 `B` 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[+B] {
42+
def prepend[U >: B](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 ListNode[+B](h: B, t: Node[B]) extends Node[B] {
46+
def prepend[U >: B](elem: U) = ListNode[U](elem, this)
47+
def head: B = h
48+
def tail = t
49+
}
4950
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)
51+
case class Nil[+B]() extends Node[B] {
52+
def prepend[U >: B](elem: U) = ListNode[U](elem, this)
5653
}
5754
```
5855

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

0 commit comments

Comments
 (0)