From 1ec7d50243a4d8a0cfc9604342da90b87224b04d Mon Sep 17 00:00:00 2001 From: Travis Lee Date: Wed, 29 Mar 2017 20:11:59 +0200 Subject: [PATCH 1/2] Rewrote lower type bounds section of tour --- .../_posts/2017-02-13-lower-type-bounds.md | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md b/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md index 4cfe283938..91d256bcbe 100644 --- a/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md +++ b/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md @@ -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) } -``` -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] { + 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 -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. From 57e659fae6b41d4e0305e587927b70a5b2e1665a Mon Sep 17 00:00:00 2001 From: Travis Lee Date: Tue, 9 May 2017 11:12:59 +0200 Subject: [PATCH 2/2] Changed type from T to B for consistency --- .../_posts/2017-02-13-lower-type-bounds.md | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md b/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md index 91d256bcbe..40f959ac89 100644 --- a/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md +++ b/tutorials/tour/_posts/2017-02-13-lower-type-bounds.md @@ -12,44 +12,44 @@ 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`. In most cases, `A` will be the type parameter of the class and `T` will be the type parameter of a method. +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. Here is an example where this is useful: ```tut:fail -trait Node[+T] { - def prepend(elem: T) +trait Node[+B] { + def prepend(elem: B): Unit } -case class ListNode[+T](h: T, t: Node[T]) extends Node[T] { - def prepend(elem: T) = ListNode[T](elem, this) - def head: T = h +case class ListNode[+B](h: B, t: Node[B]) extends Node[B] { + def prepend(elem: B) = ListNode[B](elem, this) + def head: B = h def tail = t } -case class Nil[+T]() extends Node[T] { - def prepend(elem: T) = ListNode[T](elem, this) +case class Nil[+B]() extends Node[B] { + def prepend(elem: B) = ListNode[B](elem, this) } ``` -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`. +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`. -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. +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. -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. +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. ```tut -trait Node[+T] { - def prepend[U >: T](elem: U) +trait Node[+B] { + def prepend[U >: B](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 +case class ListNode[+B](h: B, t: Node[B]) extends Node[B] { + def prepend[U >: B](elem: U) = ListNode[U](elem, this) + def head: B = h def tail = t } -case class Nil[+T]() extends Node[T] { - def prepend[U >: T](elem: U) = ListNode[U](elem, this) +case class Nil[+B]() extends Node[B] { + def prepend[U >: B](elem: U) = ListNode[U](elem, this) } ```