From 1ff5fb2f1e7c9ea6919dacd16761a4bc6dbca173 Mon Sep 17 00:00:00 2001 From: Luc Henninger Date: Thu, 22 Sep 2022 15:10:08 +0200 Subject: [PATCH] Add code tabs for _tour/lower-type-bounds --- _tour/lower-type-bounds.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/_tour/lower-type-bounds.md b/_tour/lower-type-bounds.md index f4191b425b..bac1883358 100644 --- a/_tour/lower-type-bounds.md +++ b/_tour/lower-type-bounds.md @@ -15,6 +15,8 @@ While [upper type bounds](upper-type-bounds.html) limit a type to a subtype of a Here is an example where this is useful: +{% tabs upper-type-bounds_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=upper-type-bounds_1 %} ```scala mdoc:fail trait List[+A] { def prepend(elem: A): NonEmptyList[A] = NonEmptyList(elem, this) @@ -24,6 +26,18 @@ case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A] object Nil extends List[Nothing] ``` +{% endtab %} +{% tab 'Scala 3' for=upper-type-bounds_1 %} +```scala +trait List[+A]: + def prepend(elem: A): NonEmptyList[A] = NonEmptyList(elem, this) + +case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A] + +object Nil extends List[Nothing] +``` +{% endtab %} +{% endtabs %} 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`. @@ -31,6 +45,8 @@ However, this program does _not_ compile because the parameter `elem` in `prepen 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. +{% tabs upper-type-bounds_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=upper-type-bounds_2 %} ```scala mdoc trait List[+A] { def prepend[B >: A](elem: B): NonEmptyList[B] = NonEmptyList(elem, this) @@ -40,7 +56,23 @@ case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A] object Nil extends List[Nothing] ``` +{% endtab %} +{% tab 'Scala 3' for=upper-type-bounds_2 %} +```scala +trait List[+A]: + def prepend[B >: A](elem: B): NonEmptyList[B] = NonEmptyList(elem, this) + +case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A] + +object Nil extends List[Nothing] +``` +{% endtab %} +{% endtabs %} + Now we can do the following: + +{% tabs upper-type-bounds_3 %} +{% tab 'Scala 2 and 3' for=upper-type-bounds_3 %} ```scala mdoc trait Bird case class AfricanSwallow() extends Bird @@ -65,6 +97,9 @@ val allBirds = africanSwallows.prepend(EuropeanSwallow()) // but this is a mistake! adding a list of birds widens the type arg too much. -Xlint will warn! val error = moreBirds.prepend(swallowsFromAntarctica) // List[Object] ``` +{% endtab %} +{% endtabs %} + The covariant type parameter allows `birds` to get the value of `africanSwallows`. 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]`.