From 51994bb0782a01989f6dc5e2e6e16f9f8a94a2c6 Mon Sep 17 00:00:00 2001 From: Alvin Alexander Date: Tue, 31 Jan 2023 17:01:10 -0700 Subject: [PATCH 1/3] Updated pages to be 'Scala 3 Only' --- _overviews/scala3-book/ca-context-bounds.md | 26 ++++++++++ _overviews/scala3-book/ca-given-imports.md | 17 +++++++ .../scala3-book/ca-multiversal-equality.md | 49 +++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/_overviews/scala3-book/ca-context-bounds.md b/_overviews/scala3-book/ca-context-bounds.md index 7d4faa3c4d..5a621e4b9f 100644 --- a/_overviews/scala3-book/ca-context-bounds.md +++ b/_overviews/scala3-book/ca-context-bounds.md @@ -7,6 +7,7 @@ num: 61 previous-page: ca-given-using-clauses next-page: ca-given-imports --- +Scala 3 only {% comment %} @@ -22,18 +23,34 @@ In that case you don’t have to define a parameter name, and can just provide t For example, this `maximum` method takes a _context parameter_ of type `Ord`, only to pass it on as an argument to `max`: +{% tabs context-bounds-max-named-param %} + +{% tab 'Scala 3 Only' %} + ```scala def maximum[A](xs: List[A])(using ord: Ord[A]): A = xs.reduceLeft(max(ord)) ``` +{% endtab %} + +{% endtabs %} + In that code the parameter name `ord` isn’t actually required; it can be passed on as an inferred argument to `max`, so you just state that `maximum` uses the type `Ord[A]` without giving it a name: +{% tabs context-bounds-no-param-name %} + +{% tab 'Scala 3 Only' %} + ```scala def maximum[A](xs: List[A])(using Ord[A]): A = xs.reduceLeft(max) ``` +{% endtab %} + +{% endtabs %} + ## Context bounds @@ -41,10 +58,19 @@ Given that background, a _context bound_ is a shorthand syntax for expressing th Using a context bound, the `maximum` method can be written like this: +{% tabs context-bounds-max-rewritten %} + +{% tab 'Scala 3 Only' %} + ```scala def maximum[A: Ord](xs: List[A]): A = xs.reduceLeft(max) ``` +{% endtab %} + +{% endtabs %} + + A bound like `: Ord` on a type parameter `A` of a method or class indicates a context parameter with `Ord[A]`. For more information about context bounds, see the [“What are context bounds?”](https://docs.scala-lang.org/tutorials/FAQ/context-bounds.html) section of the Scala FAQ. diff --git a/_overviews/scala3-book/ca-given-imports.md b/_overviews/scala3-book/ca-given-imports.md index 9b009001a9..b60e21f75d 100644 --- a/_overviews/scala3-book/ca-given-imports.md +++ b/_overviews/scala3-book/ca-given-imports.md @@ -7,11 +7,16 @@ num: 62 previous-page: ca-context-bounds next-page: ca-type-classes --- +Scala 3 only To make it more clear where givens in the current scope are coming from, a special form of the `import` statement is used to import `given` instances. The basic form is shown in this example: +{% tabs given-imports-basic-form %} + +{% tab 'Scala 3 Only' %} + ```scala object A: class TC @@ -23,15 +28,27 @@ object B: import A.given // import the given instance ``` +{% endtab %} + +{% endtabs %} + In this code the `import A.*` clause of object `B` imports all members of `A` *except* the `given` instance, `tc`. Conversely, the second import, `import A.given`, imports *only* that `given` instance. The two `import` clauses can also be merged into one: +{% tabs given-imports-merged %} + +{% tab 'Scala 3 Only' %} + ```scala object B: import A.{given, *} ``` +{% endtab %} + +{% endtabs %} + ## Discussion diff --git a/_overviews/scala3-book/ca-multiversal-equality.md b/_overviews/scala3-book/ca-multiversal-equality.md index dc16effb35..032dcaf90a 100644 --- a/_overviews/scala3-book/ca-multiversal-equality.md +++ b/_overviews/scala3-book/ca-multiversal-equality.md @@ -7,6 +7,7 @@ num: 64 previous-page: ca-type-classes next-page: ca-implicit-conversions --- +Scala 3 only Previously, Scala had *universal equality*: Two values of any types could be compared with each other using `==` and `!=`. @@ -44,6 +45,10 @@ d == c // false, but it compiles But with Scala 3 you can disable such comparisons. By (a) importing `scala.language.strictEquality` or (b) using the `-language:strictEquality` compiler flag, this comparison no longer compiles: +{% tabs multiversal-equality-strictEquality %} + +{% tab 'Scala 3 Only' %} + ```scala import scala.language.strictEquality @@ -55,25 +60,45 @@ println(rover == fido) // compiler error // Values of types Dog and Dog cannot be compared with == or != ``` +{% endtab %} + +{% endtabs %} + ## Enabling comparisons There are two ways to enable this comparison using the Scala 3 `CanEqual` type class. For simple cases like this, your class can *derive* the `CanEqual` class: +{% tabs multiversal-equality-derives-CanEqual %} + +{% tab 'Scala 3 Only' %} + ```scala // Option 1 case class Dog(name: String) derives CanEqual ``` +{% endtab %} + +{% endtabs %} + As you’ll see in a few moments, when you need more flexibility you can also use this syntax: +{% tabs multiversal-equality-given-CanEqual %} + +{% tab 'Scala 3 Only' %} + ```scala // Option 2 case class Dog(name: String) given CanEqual[Dog, Dog] = CanEqual.derived ``` +{% endtab %} + +{% endtabs %} + Either of those two approaches now let `Dog` instances to be compared to each other. @@ -82,11 +107,19 @@ Either of those two approaches now let `Dog` instances to be compared to each ot In a more real-world example, imagine you have an online bookstore and want to allow or disallow the comparison of physical, printed books, and audiobooks. With Scala 3 you start by enabling multiversal equality as shown in the previous example: +{% tabs multiversal-equality-strictEquality-2 %} + +{% tab 'Scala 3 Only' %} + ```scala // [1] add this import, or this command line flag: -language:strictEquality import scala.language.strictEquality ``` +{% endtab %} + +{% endtabs %} + Then create your domain objects as usual: ```scala @@ -113,6 +146,10 @@ case class AudioBook( Finally, use `CanEqual` to define which comparisons you want to allow: +{% tabs multiversal-equality-CanEqual-allow-comps %} + +{% tab 'Scala 3 Only' %} + ```scala // [3] create type class instances to define the allowed comparisons. // allow `PrintedBook == PrintedBook` @@ -131,6 +168,10 @@ val aBook = AudioBook("1984", "George Orwell", 2006, 682) println(pBook == aBook) // compiler error ``` +{% endtab %} + +{% endtabs %} + The last line of code results in this compiler error message: ```` @@ -145,12 +186,20 @@ This is how multiversal equality catches illegal type comparisons at compile tim That works as desired, but in some situations you may want to allow the comparison of physical books to audiobooks. When you want this, create these two additional equality comparisons: +{% tabs multiversal-equality-additional-comps %} + +{% tab 'Scala 3 Only' %} + ```scala // allow `PrintedBook == AudioBook`, and `AudioBook == PrintedBook` given CanEqual[PrintedBook, AudioBook] = CanEqual.derived given CanEqual[AudioBook, PrintedBook] = CanEqual.derived ``` +{% endtab %} + +{% endtabs %} + Now you can compare physical books to audiobooks without a compiler error: ```scala From 1ac6acd1e781a034e5346d7874277d827a23f59d Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Wed, 1 Feb 2023 14:01:04 +0100 Subject: [PATCH 2/3] Cross-write page Context Bounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove tag “Scala 3 only” - add a link to the page that explains context parameters and inferred terms - remove paragraph that shows anonymous context parameters - fix link to the FAQ - also cross-write the FAQ entry --- _overviews/FAQ/index.md | 4 +- _overviews/scala3-book/ca-context-bounds.md | 46 ++++++------------- .../scala3-book/ca-given-using-clauses.md | 2 +- 3 files changed, 18 insertions(+), 34 deletions(-) diff --git a/_overviews/FAQ/index.md b/_overviews/FAQ/index.md index 6179c57098..4c65c0b734 100644 --- a/_overviews/FAQ/index.md +++ b/_overviews/FAQ/index.md @@ -149,9 +149,9 @@ See [this]({{ site.baseurl }}/tutorials/FAQ/initialization-order.html). See the [Scala 2.13 Collections Guide](https://docs.scala-lang.org/overviews/collections-2.13/introduction.html). -### What are context bounds (`[T : Foo]`)? +### What are context bounds? -It's syntactic sugar for an `implicit` parameter of type `Foo[T]`. +It's syntactic sugar for a context parameter (an `implicit` parameter in Scala 2, or a `using` parameter in Scala 3). More details in this [Stack Overflow answer](https://stackoverflow.com/a/4467012). diff --git a/_overviews/scala3-book/ca-context-bounds.md b/_overviews/scala3-book/ca-context-bounds.md index 5a621e4b9f..b95482a25e 100644 --- a/_overviews/scala3-book/ca-context-bounds.md +++ b/_overviews/scala3-book/ca-context-bounds.md @@ -1,21 +1,14 @@ --- title: Context Bounds type: section -description: This page demonstrates Context Bounds in Scala 3. +description: This page demonstrates Context Bounds in Scala. languages: [zh-cn] num: 61 previous-page: ca-given-using-clauses next-page: ca-given-imports --- -Scala 3 only - -{% comment %} -- TODO: define "context parameter" -- TODO: define "synthesized" and "synthesized arguments" -{% endcomment %} - -In many situations the name of a _context parameter_ doesn’t have to be mentioned explicitly, since it’s only used by the compiler in synthesized arguments for other context parameters. +In many situations the name of a [context parameter]({% link _overviews/scala3-book/ca-given-using-clauses.md %}#using-clauses) doesn’t have to be mentioned explicitly, since it’s only used by the compiler in synthesized arguments for other context parameters. In that case you don’t have to define a parameter name, and can just provide the parameter type. @@ -23,47 +16,37 @@ In that case you don’t have to define a parameter name, and can just provide t For example, this `maximum` method takes a _context parameter_ of type `Ord`, only to pass it on as an argument to `max`: -{% tabs context-bounds-max-named-param %} - -{% tab 'Scala 3 Only' %} +{% tabs context-bounds-max-named-param class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -def maximum[A](xs: List[A])(using ord: Ord[A]): A = +def maximum[A](xs: List[A])(implicit ord: Ord[A]): A = xs.reduceLeft(max(ord)) ``` - {% endtab %} -{% endtabs %} - -In that code the parameter name `ord` isn’t actually required; it can be passed on as an inferred argument to `max`, so you just state that `maximum` uses the type `Ord[A]` without giving it a name: - -{% tabs context-bounds-no-param-name %} - -{% tab 'Scala 3 Only' %} - +{% tab 'Scala 3' %} ```scala -def maximum[A](xs: List[A])(using Ord[A]): A = - xs.reduceLeft(max) +def maximum[A](xs: List[A])(using ord: Ord[A]): A = + xs.reduceLeft(max(using ord)) ``` - {% endtab %} {% endtabs %} - ## Context bounds -Given that background, a _context bound_ is a shorthand syntax for expressing the pattern of, “a context parameter that depends on a type parameter.” +Given that background, a _context bound_ is a shorthand syntax for expressing the pattern of, “a context parameter applied to a type parameter.” Using a context bound, the `maximum` method can be written like this: {% tabs context-bounds-max-rewritten %} -{% tab 'Scala 3 Only' %} +{% tab 'Scala 2 and 3' %} ```scala -def maximum[A: Ord](xs: List[A]): A = xs.reduceLeft(max) +def maximum[A: Ord](xs: List[A]): A = + xs.reduceLeft(max) ``` {% endtab %} @@ -71,6 +54,7 @@ def maximum[A: Ord](xs: List[A]): A = xs.reduceLeft(max) {% endtabs %} -A bound like `: Ord` on a type parameter `A` of a method or class indicates a context parameter with `Ord[A]`. +A bound like `: Ord` on a type parameter `A` of a method or class indicates a context parameter with type `Ord[A]`. +Under the hood, the compiler transforms this syntax into the one shown in the Background section. -For more information about context bounds, see the [“What are context bounds?”](https://docs.scala-lang.org/tutorials/FAQ/context-bounds.html) section of the Scala FAQ. +For more information about context bounds, see the [“What are context bounds?”]({% link _overviews/FAQ/index.md %}#what-are-context-bounds) section of the Scala FAQ. diff --git a/_overviews/scala3-book/ca-given-using-clauses.md b/_overviews/scala3-book/ca-given-using-clauses.md index d762e8719a..a0c747c450 100644 --- a/_overviews/scala3-book/ca-given-using-clauses.md +++ b/_overviews/scala3-book/ca-given-using-clauses.md @@ -63,7 +63,7 @@ def renderWidget(items: List[String])(using c: Config): String = ??? {% endtab %} {% endtabs %} -By starting a parameter section with the keyword `using`, we tell the Scala compiler that at the callsite it should automatically find an argument with the correct type. +By starting a parameter section with the keyword `using`, we tell the Scala compiler that at the call-site it should automatically find an argument with the correct type. The Scala compiler thus performs **term inference**. In our call to `renderWidget(List("cart"))` the Scala compiler will see that there is a term of type `Config` in scope (the `c`) and automatically provide it to `renderWidget`. From fa7a8554af292e7a1b44fd98197dfa1c00d75bc8 Mon Sep 17 00:00:00 2001 From: Alvin Alexander Date: Fri, 3 Feb 2023 17:38:47 -0700 Subject: [PATCH 3/3] Updated 'New in Scala 3' approach --- .../scala3-book/ca-multiversal-equality.md | 53 ++----------------- 1 file changed, 4 insertions(+), 49 deletions(-) diff --git a/_overviews/scala3-book/ca-multiversal-equality.md b/_overviews/scala3-book/ca-multiversal-equality.md index 032dcaf90a..54aed55fa4 100644 --- a/_overviews/scala3-book/ca-multiversal-equality.md +++ b/_overviews/scala3-book/ca-multiversal-equality.md @@ -7,8 +7,11 @@ num: 64 previous-page: ca-type-classes next-page: ca-implicit-conversions --- -Scala 3 only +New In Scala 3 +> Multiversal Equality is a new language feature that was introduced in Scala 3. +> Because it has no equivalent in Scala 2, all code examples +> in this lesson assume you are using Scala 3. Previously, Scala had *universal equality*: Two values of any types could be compared with each other using `==` and `!=`. This came from the fact that `==` and `!=` are implemented in terms of Java’s `equals` method, which can also compare values of any two reference types. @@ -45,10 +48,6 @@ d == c // false, but it compiles But with Scala 3 you can disable such comparisons. By (a) importing `scala.language.strictEquality` or (b) using the `-language:strictEquality` compiler flag, this comparison no longer compiles: -{% tabs multiversal-equality-strictEquality %} - -{% tab 'Scala 3 Only' %} - ```scala import scala.language.strictEquality @@ -60,45 +59,25 @@ println(rover == fido) // compiler error // Values of types Dog and Dog cannot be compared with == or != ``` -{% endtab %} - -{% endtabs %} - ## Enabling comparisons There are two ways to enable this comparison using the Scala 3 `CanEqual` type class. For simple cases like this, your class can *derive* the `CanEqual` class: -{% tabs multiversal-equality-derives-CanEqual %} - -{% tab 'Scala 3 Only' %} - ```scala // Option 1 case class Dog(name: String) derives CanEqual ``` -{% endtab %} - -{% endtabs %} - As you’ll see in a few moments, when you need more flexibility you can also use this syntax: -{% tabs multiversal-equality-given-CanEqual %} - -{% tab 'Scala 3 Only' %} - ```scala // Option 2 case class Dog(name: String) given CanEqual[Dog, Dog] = CanEqual.derived ``` -{% endtab %} - -{% endtabs %} - Either of those two approaches now let `Dog` instances to be compared to each other. @@ -107,19 +86,11 @@ Either of those two approaches now let `Dog` instances to be compared to each ot In a more real-world example, imagine you have an online bookstore and want to allow or disallow the comparison of physical, printed books, and audiobooks. With Scala 3 you start by enabling multiversal equality as shown in the previous example: -{% tabs multiversal-equality-strictEquality-2 %} - -{% tab 'Scala 3 Only' %} - ```scala // [1] add this import, or this command line flag: -language:strictEquality import scala.language.strictEquality ``` -{% endtab %} - -{% endtabs %} - Then create your domain objects as usual: ```scala @@ -146,10 +117,6 @@ case class AudioBook( Finally, use `CanEqual` to define which comparisons you want to allow: -{% tabs multiversal-equality-CanEqual-allow-comps %} - -{% tab 'Scala 3 Only' %} - ```scala // [3] create type class instances to define the allowed comparisons. // allow `PrintedBook == PrintedBook` @@ -168,10 +135,6 @@ val aBook = AudioBook("1984", "George Orwell", 2006, 682) println(pBook == aBook) // compiler error ``` -{% endtab %} - -{% endtabs %} - The last line of code results in this compiler error message: ```` @@ -186,20 +149,12 @@ This is how multiversal equality catches illegal type comparisons at compile tim That works as desired, but in some situations you may want to allow the comparison of physical books to audiobooks. When you want this, create these two additional equality comparisons: -{% tabs multiversal-equality-additional-comps %} - -{% tab 'Scala 3 Only' %} - ```scala // allow `PrintedBook == AudioBook`, and `AudioBook == PrintedBook` given CanEqual[PrintedBook, AudioBook] = CanEqual.derived given CanEqual[AudioBook, PrintedBook] = CanEqual.derived ``` -{% endtab %} - -{% endtabs %} - Now you can compare physical books to audiobooks without a compiler error: ```scala