-
Notifications
You must be signed in to change notification settings - Fork 1k
Rewrote lower type bounds section of tour #760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
--- | ||
--- | ||
layout: tutorial | ||
title: Lower Type Bounds | ||
|
||
|
@@ -9,50 +9,59 @@ categories: tour | |
num: 21 | ||
next-page: inner-classes | ||
previous-page: upper-type-bounds | ||
prerequisite-knowledge: upper-type-bounds, generics, variance | ||
--- | ||
|
||
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`. | ||
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. | ||
|
||
Here is an example where this is useful: | ||
|
||
```tut | ||
case class ListNode[T](h: T, t: ListNode[T]) { | ||
def head: T = h | ||
def tail: ListNode[T] = t | ||
def prepend(elem: T): ListNode[T] = | ||
ListNode(elem, this) | ||
```tut:fail | ||
trait Node[+T] { | ||
def prepend(elem: T) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing return type here |
||
} | ||
``` | ||
|
||
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: | ||
case class ListNode[+T](h: T, t: Node[T]) extends Node[T] { | ||
def prepend(elem: T) = ListNode[T](elem, this) | ||
def head: T = h | ||
def tail = t | ||
} | ||
|
||
case class Nil[+T]() extends Node[T] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't seem to |
||
def prepend(elem: T) = ListNode[T](elem, this) | ||
} | ||
``` | ||
case class ListNode[+T](h: T, t: ListNode[T]) { ... } | ||
``` | ||
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`. | ||
|
||
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. | ||
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. | ||
|
||
Here is the corresponding code: | ||
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. | ||
|
||
```tut | ||
case class ListNode[+T](h: T, t: ListNode[T]) { | ||
trait Node[+T] { | ||
def prepend[U >: T](elem: U) | ||
} | ||
|
||
case class ListNode[+T](h: T, t: Node[T]) extends Node[T] { | ||
def prepend[U >: T](elem: U) = ListNode[U](elem, this) | ||
def head: T = h | ||
def tail: ListNode[T] = t | ||
def prepend[U >: T](elem: U): ListNode[U] = | ||
ListNode(elem, this) | ||
def tail = t | ||
} | ||
|
||
case class Nil[+T]() extends Node[T] { | ||
def prepend[U >: T](elem: U) = ListNode[U](elem, this) | ||
} | ||
``` | ||
|
||
_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. | ||
Now we can do the following: | ||
```tut | ||
trait Mammal | ||
case class AfricanSwallow() extends Mammal | ||
case class EuropeanSwallow() extends Mammal | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these can be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but then I'm not sure how to make it work with the example below. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You'd have to do e.g. |
||
|
||
Here is some code which illustrates this: | ||
|
||
```tut | ||
object LowerBoundTest extends App { | ||
val empty: ListNode[Null] = ListNode(null, null) | ||
val strList: ListNode[String] = empty.prepend("hello") | ||
.prepend("world") | ||
val anyList: ListNode[Any] = strList.prepend(12345) | ||
} | ||
val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil()) | ||
val mammalList: Node[Mammal] = africanSwallowList | ||
mammalList.prepend(new EuropeanSwallow) | ||
``` | ||
|
||
The `Node[Mammal]` can be assigned the `africanSwallowList` but then accept `EuropeanSwallow`s. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest using
B
instead ofT
, this matches the style used in the standard library