diff --git a/Gemfile.lock b/Gemfile.lock
index 6e65062be0..ed3ff1d122 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -13,7 +13,7 @@ GEM
execjs
coffee-script-source (1.11.1)
colorator (1.1.0)
- commonmarker (0.23.8)
+ commonmarker (0.23.9)
concurrent-ruby (1.2.2)
dnsruby (1.61.9)
simpleidn (~> 0.1)
@@ -219,7 +219,11 @@ GEM
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
minitest (5.18.0)
- nokogiri (1.14.2-x86_64-linux)
+ nokogiri (1.14.3-arm64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.14.3-x64-mingw-ucrt)
+ racc (~> 1.4)
+ nokogiri (1.14.3-x86_64-linux)
racc (~> 1.4)
octokit (4.25.1)
faraday (>= 1, < 3)
@@ -263,6 +267,8 @@ GEM
zeitwerk (2.6.7)
PLATFORMS
+ arm64-darwin-22
+ x64-mingw-ucrt
x86_64-linux
DEPENDENCIES
diff --git a/_config.yml b/_config.yml
index 3439515b5d..02ccc1703c 100644
--- a/_config.yml
+++ b/_config.yml
@@ -192,6 +192,14 @@ defaults:
overview-name: "Scaladoc"
layout: multipage-overview
permalink: "/scala3/guides/scaladoc/:title.html"
+ -
+ scope:
+ path: "_overviews/toolkit"
+ values:
+ partof: toolkit
+ overview-name: "The Scala Toolkit"
+ layout: multipage-overview
+ permalink: "/toolkit/:title.html"
-
scope:
path: "scala3"
@@ -203,6 +211,6 @@ highlighter: rouge
permalink: /:categories/:title.html:output_ext
baseurl:
scala3ref: "https://docs.scala-lang.org/scala3/reference"
-exclude: ["vendor"]
+exclude: ["vendor", ".metals"]
plugins:
- jekyll-redirect-from
diff --git a/_data/doc-nav-header.yml b/_data/doc-nav-header.yml
index 392012e3b5..6eddea4ed7 100644
--- a/_data/doc-nav-header.yml
+++ b/_data/doc-nav-header.yml
@@ -37,6 +37,8 @@
url: "/tutorials/scala-on-android.html"
- title: Scala with Maven
url: "/tutorials/scala-with-maven.html"
+ - title: Using the Scala Toolkit
+ url: "/toolkit/introduction.html"
- title: Reference
url: "#"
submenu:
diff --git a/_includes/_markdown/install-munit.md b/_includes/_markdown/install-munit.md
new file mode 100644
index 0000000000..e79aca2e44
--- /dev/null
+++ b/_includes/_markdown/install-munit.md
@@ -0,0 +1,51 @@
+{% altDetails install-info-box 'Getting MUnit' %}
+
+{% tabs munit-unit-test-1 class=tabs-build-tool %}
+{% tab 'Scala CLI' %}
+You can require the entire toolkit in a single line:
+```scala
+//> using dep "org.scala-lang::toolkit-test:0.1.7"
+```
+
+Alternatively, you can require just a specific version of MUnit:
+```scala
+//> using dep "org.scalameta::munit:1.0.0-M7"
+```
+{% endtab %}
+{% tab 'sbt' %}
+In your build.sbt file, you can add the dependency on toolkit-test:
+```scala
+lazy val example = project.in(file("example"))
+ .settings(
+ scalaVersion := "3.2.2",
+ libraryDependencies += "org.scala-lang" %% "toolkit-test" % "0.1.7" % Test
+ )
+```
+Here the `Test` configuration means that the dependency is only used by the source files in `example/src/test`.
+
+Alternatively, you can require just a specific version of MUnit:
+```scala
+libraryDependencies += "org.scalameta" %% "munit" % "1.0.0-M7" % Test
+```
+{% endtab %}
+{% tab 'Mill' %}
+In your build.sc file, you can add a `test` object extending `Tests` and `TestModule.Munit`:
+```scala
+object example extends ScalaModule {
+ def scalaVersion = "3.2.2"
+ object test extends Tests with TestModule.Munit {
+ def ivyDeps =
+ Agg(
+ ivy"org.scala-lang::toolkit-test:0.1.7"
+ )
+ }
+}
+```
+
+Alternatively, you can require just a specific version of MUnit:
+```scala
+ivy"org.scalameta::munit:1.0.0-M7"
+```
+{% endtab %}
+{% endtabs %}
+{% endaltDetails %}
diff --git a/_includes/_markdown/install-os-lib.md b/_includes/_markdown/install-os-lib.md
new file mode 100644
index 0000000000..0d3057c0e9
--- /dev/null
+++ b/_includes/_markdown/install-os-lib.md
@@ -0,0 +1,46 @@
+{% altDetails require-info-box 'Getting OS-Lib' %}
+
+{% tabs oslib-install class=tabs-build-tool %}
+{% tab 'Scala CLI' %}
+You can require the entire toolkit in a single line:
+```scala
+//> using toolkit "latest"
+```
+
+Alternatively, you can require just a specific version of OS-Lib:
+```scala
+//> using dep "com.lihaoyi::os-lib:0.9.1"
+```
+{% endtab %}
+{% tab 'sbt' %}
+In your `build.sbt`, you can add a dependency on the toolkit:
+```scala
+lazy val example = project.in(file("example"))
+ .settings(
+ scalaVersion := "3.2.2",
+ libraryDependencies += "org.scala-lang" %% "toolkit" % "0.1.7"
+ )
+```
+Alternatively, you can require just a specific version of OS-Lib:
+```scala
+libraryDependencies += "com.lihaoyi" %% "os-lib" % "0.9.1"
+```
+{% endtab %}
+{% tab 'Mill' %}
+In your `build.sc` file, you can add a dependency on the Toolkit:
+```scala
+object example extends ScalaModule {
+ def scalaVersion = "3.2.2"
+ def ivyDeps =
+ Agg(
+ ivy"org.scala-lang::toolkit:0.1.7"
+ )
+}
+```
+Alternatively, you can require just a specific version of OS-Lib:
+```scala
+ivy"com.lihaoyi::os-lib:0.9.1"
+```
+{% endtab %}
+{% endtabs %}
+{% endaltDetails %}
diff --git a/_includes/_markdown/install-sttp.md b/_includes/_markdown/install-sttp.md
new file mode 100644
index 0000000000..16cd90f485
--- /dev/null
+++ b/_includes/_markdown/install-sttp.md
@@ -0,0 +1,47 @@
+{% altDetails install-info-box 'Getting sttp' %}
+
+{% tabs sttp-install-methods class=tabs-build-tool%}
+{% tab 'Scala CLI' %}
+You can require the entire toolkit in a single line:
+```scala
+//> using toolkit "latest"
+```
+
+Alternatively, you can require just a specific version of sttp:
+```scala
+//> using dep "com.softwaremill.sttp.client4::core:4.0.0-M1"
+```
+{% endtab %}
+{% tab 'sbt' %}
+In your build.sbt file, you can add a dependency on the Toolkit:
+```scala
+lazy val example = project.in(file("example"))
+ .settings(
+ scalaVersion := "3.2.2",
+ libraryDependencies += "org.scala-lang" %% "toolkit" % "0.1.7"
+ )
+```
+
+Alternatively, you can require just a specific version of sttp:
+```scala
+libraryDependencies += "com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1"
+```
+{% endtab %}
+{% tab 'Mill' %}
+In your build.sc file, you can add a dependency on the Toolkit:
+```scala
+object example extends ScalaModule {
+ def scalaVersion = "3.2.2"
+ def ivyDeps =
+ Agg(
+ ivy"org.scala-lang::toolkit:0.1.7"
+ )
+}
+```
+Alternatively, you can require just a specific version of sttp:
+```scala
+ivy"com.softwaremill.sttp.client4::core:4.0.0-M1"
+```
+{% endtab %}
+{% endtabs %}
+{% endaltDetails %}
diff --git a/_includes/_markdown/install-upickle.md b/_includes/_markdown/install-upickle.md
new file mode 100644
index 0000000000..2160f4dc91
--- /dev/null
+++ b/_includes/_markdown/install-upickle.md
@@ -0,0 +1,46 @@
+{% altDetails install-info-box 'Getting upickle' %}
+
+{% tabs upickle-install-methods class=tabs-build-tool %}
+{% tab 'Scala CLI' %}
+Using Scala CLI, you can require the entire toolkit in a single line:
+```scala
+//> using toolkit "latest"
+```
+
+Alternatively, you can require just a specific version of UPickle:
+```scala
+//> using dep "com.lihaoyi::upickle:3.1.0
+```
+{% endtab %}
+{% tab 'sbt' %}
+In your build.sbt file, you can add the dependency on the Toolkit:
+```scala
+lazy val example = project.in(file("example"))
+ .settings(
+ scalaVersion := "3.2.2",
+ libraryDependencies += "org.scala-lang" %% "toolkit" % "0.1.7"
+ )
+```
+Alternatively, you can require just a specific version of UPickle:
+```scala
+libraryDependencies += "com.lihaoyi" %% "upickle" % "3.1.0"
+```
+{% endtab %}
+{% tab 'Mill' %}
+In your build.sc file, you can add the dependency to the upickle library:
+```scala
+object example extends ScalaModule {
+ def scalaVersion = "3.2.2"
+ def ivyDeps =
+ Agg(
+ ivy"org.scala-lang::toolkit:0.1.7"
+ )
+}
+```
+Alternatively, you can require just a specific version of UPickle:
+```scala
+ivy"com.lihaoyi::upickle:3.1.0"
+```
+{% endtab %}
+{% endtabs %}
+{% endaltDetails %}
diff --git a/_includes/markdown.html b/_includes/markdown.html
new file mode 100644
index 0000000000..cd79243a41
--- /dev/null
+++ b/_includes/markdown.html
@@ -0,0 +1,3 @@
+{%if include.selector%}<{{include.selector}} {%if include.classes%}class="{{include.classes}}"{%endif%} {%if include.id%}id="{{include.id}}{%endif%}">{%endif%}
+ {% capture markdown %}{% include {{include.path}} %}{% endcapture %}{{ markdown | markdownify }}
+{%if include.selector%}{{include.selector}}>{%endif%}
diff --git a/_includes/version-specific-notice.html b/_includes/version-specific-notice.html
index b7a5a6f291..1a9ea6832b 100644
--- a/_includes/version-specific-notice.html
+++ b/_includes/version-specific-notice.html
@@ -3,10 +3,14 @@
{% if include.language == 'scala3' %}
This doc page is specific to Scala 3,
- and may cover new concepts not available in Scala 2.
+ and may cover new concepts not available in Scala 2. Unless
+ otherwise stated, all the code examples in this page assume
+ you are using Scala 3.
{% else if include.language == 'scala2' %}
This doc page is specific to features shipped in Scala 2,
- which have either been removed in Scala 3 or replaced by an alternative.
+ which have either been removed in Scala 3 or replaced by an
+ alternative. Unless otherwise stated, all the code examples
+ in this page assume you are using Scala 2.
{% endif %}
diff --git a/_overviews/contribute/add-guides.md b/_overviews/contribute/add-guides.md
index 7598ad5f63..5a4bed14f7 100644
--- a/_overviews/contribute/add-guides.md
+++ b/_overviews/contribute/add-guides.md
@@ -83,7 +83,7 @@ clarifications, etc.
## Code blocks
It's common for various kinds of documents to require code examples.
-You can contribute code in a markdown document by either
+You can contribute code in a Markdown document by either
- in-line by putting backticks around it,
- surrounding by triple backticks,
- or indenting it by 4 spaces, e.g.:
@@ -92,6 +92,7 @@ You can contribute code in a markdown document by either
inline example: `val x = 23`
block example:
+
```scala
println("hello")
```
@@ -103,9 +104,31 @@ indented example:
### Scala 2 vs Scala 3
-Sometimes you would like to compare between Scala 2 and Scala 3 in a document, for example in
-our [Hello World][hello-world] chapter of the Scala Book. Here is an example of how you
-can generate the same tabs in markdown with the `tabs` directive and class `tabs-scala-version`:
+Our goal is to have a unified documentation that covers both Scala 2 and Scala 3. In many cases, the
+code examples are the same in both Scala 2 and Scala 3, but sometimes there are some syntactic
+differences. In some less common cases, a page may explain a concept that is new in Scala 3 and has
+no equivalent in Scala 2, or a concept that has been removed in Scala 3. In all the cases, the
+documentation should clearly "label" the code examples so that the readers know in which versions
+of Scala they are valid.
+
+The following sections explain how to properly "label" the code examples.
+
+#### Labelling the code snippets of a page documenting a concept available in both Scala 2 and Scala 3
+
+When the content of a page not specific to Scala 2 or Scala 3, like for example our
+[Hello World][hello-world] chapter of the Scala Book, the code snippets should show both the
+Scala 2 and Scala 3 syntax. We achieve this by labelling the code snippets in tabs according
+to the following rules:
+
+- if the idiomatic syntax is different in Scala 2 and Scala 3, we create two tabs,
+ “Scala 2” and “Scala 3”, showing the corresponding syntax
+- if the code snippet is idiomatic in both Scala 2 and Scala 3, we create a single tab,
+ “Scala 2 and 3”
+- if the code snippet is valid only in Scala 2 or Scala 3, we create a single tab,
+ “Scala 2 Only” or “Scala 3 Only”
+
+Here is an example of how you
+can generate such tabs in Markdown with the `tabs` directive and class `tabs-scala-version`:
~~~liquid
@@ -161,6 +184,32 @@ a parameter `for=tab-group` as in this example:
~~~
+#### Labelling an entire page documenting a concept that is specific to a Scala version
+
+When the content of a page explains a concept that is new in Scala 3 and has no
+equivalent in Scala 2 (e.g. [TASTy]({% link scala3/guides/tasty-overview.md %})),
+or a concept that has been removed in Scala 3, we label the entire page instead
+of labelling each code example.
+
+We achieve this by setting a couple of a attributes in the [YAML front
+matter](https://jekyllrb.com/docs/front-matter/) of the Markdown file. For
+instance, for a page that is specific to Scala 3:
+
+~~~ yaml
+scala3: true
+versionSpecific: true
+~~~
+
+Or, for a page that is specific to Scala 2:
+
+~~~ yaml
+scala2: true
+versionSpecific: true
+~~~
+
+Please note that when the entire page is labelled, its code examples do not
+need to have tabs.
+
### Typechecked Examples
The site build process uses [mdoc](https://scalameta.org/mdoc/) to typecheck
diff --git a/_overviews/core/futures.md b/_overviews/core/futures.md
index 9704cd57af..aac3aa4b4f 100644
--- a/_overviews/core/futures.md
+++ b/_overviews/core/futures.md
@@ -14,7 +14,8 @@ permalink: /overviews/core/:title.html
## Introduction
Futures provide a way to reason about performing many operations
-in parallel-- in an efficient and non-blocking way.
+in parallel -- in an efficient and non-blocking way.
+
A [`Future`](https://www.scala-lang.org/api/current/scala/concurrent/Future.html)
is a placeholder object for a value that may not yet exist.
Generally, the value of the Future is supplied concurrently and can subsequently be used.
@@ -283,7 +284,7 @@ Completion can take one of two forms:
A `Future` has an important property that it may only be assigned
once.
Once a `Future` object is given a value or an exception, it becomes
-in effect immutable-- it can never be overwritten.
+in effect immutable -- it can never be overwritten.
The simplest way to create a future object is to invoke the `Future.apply`
method which starts an asynchronous computation and returns a
@@ -335,8 +336,8 @@ To obtain the list of friends of a user, a request
has to be sent over a network, which can take a long time.
This is illustrated with the call to the method `getFriends` that returns `List[Friend]`.
To better utilize the CPU until the response arrives, we should not
-block the rest of the program-- this computation should be scheduled
-asynchronously. The `Future.apply` method does exactly that-- it performs
+block the rest of the program -- this computation should be scheduled
+asynchronously. The `Future.apply` method does exactly that -- it performs
the specified computation block concurrently, in this case sending
a request to the server and waiting for a response.
@@ -396,7 +397,7 @@ We are often interested in the result of the computation, not just its
side-effects.
In many future implementations, once the client of the future becomes interested
-in its result, it has to block its own computation and wait until the future is completed--
+in its result, it has to block its own computation and wait until the future is completed --
only then can it use the value of the future to continue its own computation.
Although this is allowed by the Scala `Future` API as we will show later,
from a performance point of view a better way to do it is in a completely
@@ -428,7 +429,7 @@ value is a `Throwable`.
Coming back to our social network example, let's assume we want to
fetch a list of our own recent posts and render them to the screen.
We do so by calling a method `getRecentPosts` which returns
-a `List[String]`-- a list of recent textual posts:
+a `List[String]` -- a list of recent textual posts:
{% tabs futures-05 class=tabs-scala-version %}
{% tab 'Scala 2' for=futures-05 %}
@@ -650,7 +651,7 @@ some other currency. We would have to repeat this pattern within the
to reason about.
Second, the `purchase` future is not in the scope with the rest of
-the code-- it can only be acted upon from within the `foreach`
+the code -- it can only be acted upon from within the `foreach`
callback. This means that other parts of the application do not
see the `purchase` future and cannot register another `foreach`
callback to it, for example, to sell some other currency.
@@ -760,7 +761,7 @@ Here is an example of `flatMap` and `withFilter` usage within for-comprehensions
{% endtabs %}
The `purchase` future is completed only once both `usdQuote`
-and `chfQuote` are completed-- it depends on the values
+and `chfQuote` are completed -- it depends on the values
of both these futures so its own computation cannot begin
earlier.
@@ -1086,7 +1087,7 @@ Here is an example of how to block on the result of a future:
In the case that the future fails, the caller is forwarded the
exception that the future is failed with. This includes the `failed`
-projection-- blocking on it results in a `NoSuchElementException`
+projection -- blocking on it results in a `NoSuchElementException`
being thrown if the original future is completed successfully.
Alternatively, calling `Await.ready` waits until the future becomes
@@ -1095,7 +1096,7 @@ that method will not throw an exception if the future is failed.
The `Future` trait implements the `Awaitable` trait with methods
`ready()` and `result()`. These methods cannot be called directly
-by the clients-- they can only be called by the execution context.
+by the clients -- they can only be called by the execution context.
@@ -1105,8 +1106,8 @@ When asynchronous computations throw unhandled exceptions, futures
associated with those computations fail. Failed futures store an
instance of `Throwable` instead of the result value. `Future`s provide
the `failed` projection method, which allows this `Throwable` to be
-treated as the success value of another `Future`. The following special
-exceptions are treated differently:
+treated as the success value of another `Future`.
+The following exceptions receive special treatment:
1. `scala.runtime.NonLocalReturnControl[_]` -- this exception holds a value
associated with the return. Typically, `return` constructs in method
@@ -1121,11 +1122,225 @@ behind this is to prevent propagation of critical and control-flow related
exceptions normally not handled by the client code and at the same time inform
the client in which future the computation failed.
-Fatal exceptions (as determined by `NonFatal`) are rethrown in the thread executing
+Fatal exceptions (as determined by `NonFatal`) are rethrown from the thread executing
the failed asynchronous computation. This informs the code managing the executing
threads of the problem and allows it to fail fast, if necessary. See
[`NonFatal`](https://www.scala-lang.org/api/current/scala/util/control/NonFatal$.html)
-for a more precise description of the semantics.
+for a more precise description of which exceptions are considered fatal.
+
+`ExecutionContext.global` handles fatal exceptions by printing a stack trace, by default.
+
+A fatal exception means that the `Future` associated with the computation will never complete.
+That is, "fatal" means that the error is not recoverable for the `ExecutionContext`
+and is also not intended to be handled by user code. By contrast, application code may
+attempt recovery from a "failed" `Future`, which has completed but with an exception.
+
+An execution context can be customized with a reporter that handles fatal exceptions.
+See the factory methods [`fromExecutor`](https://www.scala-lang.org/api/current/scala/concurrent/ExecutionContext$.html#fromExecutor(e:java.util.concurrent.Executor,reporter:Throwable=%3EUnit):scala.concurrent.ExecutionContextExecutor)
+and [`fromExecutorService`](https://www.scala-lang.org/api/current/scala/concurrent/ExecutionContext$.html#fromExecutorService(e:java.util.concurrent.ExecutorService,reporter:Throwable=%3EUnit):scala.concurrent.ExecutionContextExecutorService).
+
+Since it is necessary to set the [`UncaughtExceptionHandler`](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Thread.UncaughtExceptionHandler.html)
+for executing threads, as a convenience, when passed a `null` executor,
+`fromExecutor` will create a context that is configured the same as `global`,
+but with the supplied reporter for handling exceptions.
+
+The following example demonstrates how to obtain an `ExecutionContext` with custom error handling
+and also shows the result of different exceptions, as described above:
+
+{% tabs exceptions class=tabs-scala-version %}
+{% tab 'Scala 2' for=exceptions %}
+~~~ scala
+import java.util.concurrent.{ForkJoinPool, TimeoutException}
+import scala.concurrent.{Await, ExecutionContext, Future}
+import scala.concurrent.duration.DurationInt
+import scala.util.{Failure, Success}
+
+object Test extends App {
+ def crashing(): Int = throw new NoSuchMethodError("test")
+ def failing(): Int = throw new NumberFormatException("test")
+ def interrupt(): Int = throw new InterruptedException("test")
+ def erroring(): Int = throw new AssertionError("test")
+
+ // computations can fail in the middle of a chain of combinators, after the initial Future job has completed
+ def testCrashes()(implicit ec: ExecutionContext): Future[Int] =
+ Future.unit.map(_ => crashing())
+ def testFails()(implicit ec: ExecutionContext): Future[Int] =
+ Future.unit.map(_ => failing())
+ def testInterrupted()(implicit ec: ExecutionContext): Future[Int] =
+ Future.unit.map(_ => interrupt())
+ def testError()(implicit ec: ExecutionContext): Future[Int] =
+ Future.unit.map(_ => erroring())
+
+ // Wait for 1 second for the the completion of the passed `future` value and print it
+ def check(future: Future[Int]): Unit =
+ try {
+ Await.ready(future, 1.second)
+ for (completion <- future.value) {
+ println(s"completed $completion")
+ // In case of failure, also print the cause of the exception, when defined
+ completion match {
+ case Failure(exception) if exception.getCause != null =>
+ println(s" caused by ${exception.getCause}")
+ _ => ()
+ }
+ }
+ } catch {
+ // If the future value did not complete within 1 second, the call
+ // to `Await.ready` throws a TimeoutException
+ case _: TimeoutException => println(s"did not complete")
+ }
+
+ def reporter(t: Throwable) = println(s"reported $t")
+
+ locally {
+ // using the `global` implicit context
+ import ExecutionContext.Implicits._
+ // a successful Future
+ check(Future(42)) // completed Success(42)
+ // a Future that completes with an application exception
+ check(Future(failing())) // completed Failure(java.lang.NumberFormatException: test)
+ // same, but the exception is thrown somewhere in the chain of combinators
+ check(testFails()) // completed Failure(java.lang.NumberFormatException: test)
+ // a Future that does not complete because of a linkage error;
+ // the trace is printed to stderr by default
+ check(testCrashes()) // did not complete
+ // a Future that completes with an operational exception that is wrapped
+ check(testInterrupted()) // completed Failure(java.util.concurrent.ExecutionException: Boxed Exception)
+ // caused by java.lang.InterruptedException: test
+ // a Future that completes due to a failed assert, which is bad for the app,
+ // but is handled the same as interruption
+ check(testError()) // completed Failure(java.util.concurrent.ExecutionException: Boxed Exception)
+ // caused by java.lang.AssertionError: test
+ }
+ locally {
+ // same as `global`, but adds a custom reporter that will handle uncaught
+ // exceptions and errors reported to the context
+ implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(null, reporter)
+ check(testCrashes()) // reported java.lang.NoSuchMethodError: test
+ // did not complete
+ }
+ locally {
+ // does not handle uncaught exceptions; the executor would have to be
+ // configured separately
+ val executor = ForkJoinPool.commonPool()
+ implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(executor, reporter)
+ // the reporter is not invoked and the Future does not complete
+ check(testCrashes()) // did not complete
+ }
+ locally {
+ // sample minimal configuration for a context and underlying pool that
+ // use the reporter
+ val handler: Thread.UncaughtExceptionHandler =
+ (_: Thread, t: Throwable) => reporter(t)
+ val executor = new ForkJoinPool(
+ Runtime.getRuntime.availableProcessors,
+ ForkJoinPool.defaultForkJoinWorkerThreadFactory, // threads use the pool's handler
+ handler,
+ /*asyncMode=*/ false
+ )
+ implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(executor, reporter)
+ check(testCrashes()) // reported java.lang.NoSuchMethodError: test
+ // did not complete
+ }
+}
+~~~
+{% endtab %}
+
+{% tab 'Scala 3' for=exceptions %}
+~~~ scala
+import java.util.concurrent.{ForkJoinPool, TimeoutException}
+import scala.concurrent.{Await, ExecutionContext, Future}
+import scala.concurrent.duration.DurationInt
+import scala.util.{Failure, Success}
+
+def crashing(): Int = throw new NoSuchMethodError("test")
+def failing(): Int = throw new NumberFormatException("test")
+def interrupt(): Int = throw new InterruptedException("test")
+def erroring(): Int = throw new AssertionError("test")
+
+// computations can fail in the middle of a chain of combinators,
+// after the initial Future job has completed
+def testCrashes()(using ExecutionContext): Future[Int] =
+ Future.unit.map(_ => crashing())
+def testFails()(using ExecutionContext): Future[Int] =
+ Future.unit.map(_ => failing())
+def testInterrupted()(using ExecutionContext): Future[Int] =
+ Future.unit.map(_ => interrupt())
+def testError()(using ExecutionContext): Future[Int] =
+ Future.unit.map(_ => erroring())
+
+// Wait for 1 second for the the completion of the passed `future` value and print it
+def check(future: Future[Int]): Unit =
+ try
+ Await.ready(future, 1.second)
+ for completion <- future.value do
+ println(s"completed $completion")
+ // In case of failure, also print the cause of the exception, when defined
+ completion match
+ case Failure(exception) if exception.getCause != null =>
+ println(s" caused by ${exception.getCause}")
+ case _ => ()
+ catch
+ // If the future value did not complete within 1 second, the call
+ // to `Await.ready` throws a TimeoutException
+ case _: TimeoutException => println(s"did not complete")
+
+def reporter(t: Throwable) = println(s"reported $t")
+
+@main def test(): Unit =
+ locally:
+ // using the `global` implicit context
+ import ExecutionContext.Implicits.given
+ // a successful Future
+ check(Future(42)) // completed Success(42)
+ // a Future that completes with an application exception
+ check(Future(failing())) // completed Failure(java.lang.NumberFormatException: test)
+ // same, but the exception is thrown somewhere in the chain of combinators
+ check(testFails()) // completed Failure(java.lang.NumberFormatException: test)
+ // a Future that does not complete because of a linkage error;
+ // the trace is printed to stderr by default
+ check(testCrashes()) // did not complete
+ // a Future that completes with an operational exception that is wrapped
+ check(testInterrupted()) // completed Failure(java.util.concurrent.ExecutionException: Boxed Exception)
+ // caused by java.lang.InterruptedException: test
+ // a Future that completes due to a failed assert, which is bad for the app,
+ // but is handled the same as interruption
+ check(testError()) // completed Failure(java.util.concurrent.ExecutionException: Boxed Exception)
+ // caused by java.lang.AssertionError: test
+
+ locally:
+ // same as `global`, but adds a custom reporter that will handle uncaught
+ // exceptions and errors reported to the context
+ given ExecutionContext = ExecutionContext.fromExecutor(null, reporter)
+ check(testCrashes()) // reported java.lang.NoSuchMethodError: test
+ // did not complete
+
+ locally:
+ // does not handle uncaught exceptions; the executor would have to be
+ // configured separately
+ val executor = ForkJoinPool.commonPool()
+ given ExecutionContext = ExecutionContext.fromExecutor(executor, reporter)
+ // the reporter is not invoked and the Future does not complete
+ check(testCrashes()) // did not complete
+
+ locally:
+ // sample minimal configuration for a context and underlying pool that
+ // use the reporter
+ val handler: Thread.UncaughtExceptionHandler =
+ (_: Thread, t: Throwable) => reporter(t)
+ val executor = new ForkJoinPool(
+ Runtime.getRuntime.availableProcessors,
+ ForkJoinPool.defaultForkJoinWorkerThreadFactory, // threads use the pool's handler
+ handler,
+ /*asyncMode=*/ false
+ )
+ given ExecutionContext = ExecutionContext.fromExecutor(executor, reporter)
+ check(testCrashes()) // reported java.lang.NoSuchMethodError: test
+ // did not complete
+end test
+~~~
+{% endtab %}
+{% endtabs %}
## Promises
@@ -1226,7 +1441,7 @@ continues its computation, and finally completes the future `f` with a
valid result, by completing promise `p`.
Promises can also be completed with a `complete` method which takes
-a potential value `Try[T]`-- either a failed result of type `Failure[Throwable]` or a
+a potential value `Try[T]` -- either a failed result of type `Failure[Throwable]` or a
successful result of type `Success[T]`.
Analogous to `success`, calling `failure` and `complete` on a promise that has already
diff --git a/_overviews/core/implicit-classes.md b/_overviews/core/implicit-classes.md
index d809dc6217..eca05c593d 100644
--- a/_overviews/core/implicit-classes.md
+++ b/_overviews/core/implicit-classes.md
@@ -7,6 +7,12 @@ partof: implicit-classes
languages: [zh-cn]
permalink: /overviews/core/:title.html
+versionSpecific: true
+scala2: true
+---
+
+In Scala 3, implicit classes are still supported for compatibility reasons but the recommended way to achieve the same result is to use [extension methods]({% link _overviews/scala3-book/ca-extension-methods.md %}).
+
---
**Josh Suereth**
diff --git a/_overviews/core/string-interpolation.md b/_overviews/core/string-interpolation.md
index 56fdae8f37..e2ad7b2366 100644
--- a/_overviews/core/string-interpolation.md
+++ b/_overviews/core/string-interpolation.md
@@ -232,7 +232,7 @@ implicit class JsonHelper(val sc: StringContext) extends AnyVal {
def json(args: Any*): JSONObject = {
val strings = sc.parts.iterator
val expressions = args.iterator
- var buf = new StringBuilder(strings.next())
+ val buf = new StringBuilder(strings.next())
while (strings.hasNext) {
buf.append(expressions.next())
buf.append(strings.next())
@@ -249,7 +249,7 @@ extension (sc: StringContext)
def json(args: Any*): JSONObject =
val strings = sc.parts.iterator
val expressions = args.iterator
- var buf = new StringBuilder(strings.next())
+ val buf = new StringBuilder(strings.next())
while strings.hasNext do
buf.append(expressions.next())
buf.append(strings.next())
diff --git a/_overviews/scala3-book/ca-context-bounds.md b/_overviews/scala3-book/ca-context-bounds.md
index 2a692ed257..fee7cbd146 100644
--- a/_overviews/scala3-book/ca-context-bounds.md
+++ b/_overviews/scala3-book/ca-context-bounds.md
@@ -4,11 +4,11 @@ type: section
description: This page demonstrates Context Bounds in Scala.
languages: [zh-cn]
num: 61
-previous-page: ca-given-using-clauses
+previous-page: ca-context-parameters
next-page: ca-given-imports
---
-In many situations the name of a [context parameter]({% link _overviews/scala3-book/ca-given-using-clauses.md %}#using-clauses) does not have to be mentioned explicitly, since it is 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-context-parameters.md %}#context-parameters) does not have to be mentioned explicitly, since it is 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.
diff --git a/_overviews/scala3-book/ca-context-parameters.md b/_overviews/scala3-book/ca-context-parameters.md
new file mode 100644
index 0000000000..81ca5f7023
--- /dev/null
+++ b/_overviews/scala3-book/ca-context-parameters.md
@@ -0,0 +1,157 @@
+---
+title: Context Parameters
+type: section
+description: This page demonstrates how to declare context parameters, and how the compiler infers them at call-site.
+languages: [zh-cn]
+num: 60
+previous-page: ca-extension-methods
+next-page: ca-context-bounds
+redirect_from: /scala3/book/ca-given-using-clauses.html
+---
+
+Scala offers two important features for contextual abstraction:
+
+- **Context Parameters** allow you to specify parameters that, at the call-site, can be omitted by the programmer and should be automatically provided by the context.
+- **Given Instances** (in Scala 3) or **Implicit Definitions** (in Scala 2) are terms that can be used by the Scala compiler to fill in the missing arguments.
+
+## Context Parameters
+
+When designing a system, often context information like _configuration_ or settings need to be provided to the different components of your system.
+One common way to achieve this is by passing the configuration as additional argument to your methods.
+
+In the following example, we define a case class `Config` to model some website configuration and pass it around in the different methods.
+
+{% tabs example %}
+{% tab 'Scala 2 and 3' %}
+```scala
+case class Config(port: Int, baseUrl: String)
+
+def renderWebsite(path: String, config: Config): String =
+ "" + renderWidget(List("cart"), config) + ""
+
+def renderWidget(items: List[String], config: Config): String = ???
+
+val config = Config(8080, "docs.scala-lang.org")
+renderWebsite("/home", config)
+```
+{% endtab %}
+{% endtabs %}
+
+Let us assume that the configuration does not change throughout most of our code base.
+Passing `config` to each and every method call (like `renderWidget`) becomes very tedious and makes our program more difficult to read, since we need to ignore the `config` argument.
+
+### Marking parameters as contextual
+
+We can mark some parameters of our methods as _contextual_.
+
+{% tabs 'contextual-parameters' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+def renderWebsite(path: String)(implicit config: Config): String =
+ "" + renderWidget(List("cart")) + ""
+ // ^
+ // no argument config required anymore
+
+def renderWidget(items: List[String])(implicit config: Config): String = ???
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+def renderWebsite(path: String)(using config: Config): String =
+ "" + renderWidget(List("cart")) + ""
+ // ^
+ // no argument config required anymore
+
+def renderWidget(items: List[String])(using config: Config): String = ???
+```
+{% endtab %}
+{% endtabs %}
+
+By starting a parameter section with the keyword `using` in Scala 3 or `implicit` in Scala 2, we tell the 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 `config`) and automatically provide it to `renderWidget`.
+So the program is equivalent to the one above.
+
+In fact, since we do not need to refer to `config` in our implementation of `renderWebsite` anymore, we can even omit its name in the signature in Scala 3:
+
+{% tabs 'anonymous' %}
+{% tab 'Scala 3 Only' %}
+```scala
+// no need to come up with a parameter name
+// vvvvvvvvvvvvv
+def renderWebsite(path: String)(using Config): String =
+ "" + renderWidget(List("cart")) + ""
+```
+{% endtab %}
+{% endtabs %}
+
+In Scala 2, the name of implicit parameters is still mandatory.
+
+### Explicitly providing contextual arguments
+
+We have seen how to _abstract_ over contextual parameters and that the Scala compiler can provide arguments automatically for us.
+But how can we specify which configuration to use for our call to `renderWebsite`?
+
+{% tabs 'explicit' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+We explicitly supply the argument value as if it was a regular argument:
+```scala
+renderWebsite("/home")(config)
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+Like we specified our parameter section with `using`, we can also explicitly provide contextual arguments with `using`:
+```scala
+renderWebsite("/home")(using config)
+```
+{% endtab %}
+{% endtabs %}
+
+Explicitly providing contextual parameters can be useful if we have multiple different values in scope that would make sense, and we want to make sure that the correct one is passed to the function.
+
+For all other cases, as we will see in the next section, there is also another way to bring contextual values into scope.
+
+## Given Instances (Implicit Definitions in Scala 2)
+
+We have seen that we can explicitly pass arguments as contextual parameters.
+However, if there is _a single canonical value_ for a particular type, there is another preferred way to make it available to the Scala compiler: by marking it as `given` in Scala 3 or `implicit` in Scala 2.
+
+{% tabs 'instances' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+implicit val config: Config = Config(8080, "docs.scala-lang.org")
+// ^^^^^^
+// this is the value the Scala compiler will infer
+// as argument to contextual parameters of type Config
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+val config = Config(8080, "docs.scala-lang.org")
+
+// this is the type that we want to provide the
+// canonical value for
+// vvvvvv
+given Config = config
+// ^^^^^^
+// this is the value the Scala compiler will infer
+// as argument to contextual parameters of type Config
+```
+{% endtab %}
+{% endtabs %}
+
+In the above example we specify that whenever a contextual parameter of type `Config` is omitted in the current scope, the compiler should infer `config` as an argument.
+
+Having defined a canonical value for the type `Config`, we can call `renderWebsite` as follows:
+
+```scala
+renderWebsite("/home")
+// ^
+// again no argument
+```
+
+A detailed guide to where Scala looks for canonical values can be found in [the FAQ]({% link _overviews/FAQ/index.md %}#where-does-scala-look-for-implicits).
+
+[reference]: {{ site.scala3ref }}/overview.html
+[blog-post]: /2020/11/06/explicit-term-inference-in-scala-3.html
diff --git a/_overviews/scala3-book/ca-contextual-abstractions-intro.md b/_overviews/scala3-book/ca-contextual-abstractions-intro.md
index 4174bcf5ee..5b703e5e6f 100644
--- a/_overviews/scala3-book/ca-contextual-abstractions-intro.md
+++ b/_overviews/scala3-book/ca-contextual-abstractions-intro.md
@@ -11,8 +11,7 @@ next-page: ca-extension-methods
## Background
-Implicits in Scala 2 were a major distinguishing design feature.
-They are *the* fundamental way to abstract over context.
+Contextual abstractions are a way to abstract over context.
They represent a unified paradigm with a great variety of use cases, among them:
- Implementing type classes
@@ -21,24 +20,24 @@ They represent a unified paradigm with a great variety of use cases, among them:
- Expressing capabilities
- Computing new types, and proving relationships between them
-Since then, other languages have followed suit, e.g., Rust’s traits or Swift’s protocol extensions.
+Other languages have been influenced by Scala in this regard. E.g., Rust’s traits or Swift’s protocol extensions.
Design proposals are also on the table for Kotlin as compile time dependency resolution, for C# as Shapes and Extensions or for F# as Traits.
-Implicits are also a common feature of theorem provers such as Coq or Agda.
+Contextual abstractions are also a common feature of theorem provers such as Coq or Agda.
-Even though these designs use different terminology, they’re all variants of the core idea of *term inference*:
-Given a type, the compiler synthesizes a “canonical” term that has that type.
+Even though these designs use different terminology, they’re all variants of the core idea of **term inference**: given a type, the compiler synthesizes a “canonical” term that has that type.
+## Scala 3 Redesign
-## Redesign
+In Scala 2, contextual abstractions are supported by marking definitions (methods and values) or parameters as `implicit` (see [Context Parameters]({% link _overviews/scala3-book/ca-context-parameters.md %})).
-Scala 3 includes a redesign of contextual abstractions in Scala.
+Scala 3 includes a redesign of contextual abstractions.
While these concepts were gradually “discovered” in Scala 2, they’re now well known and understood, and the redesign takes advantage of that knowledge.
The design of Scala 3 focuses on **intent** rather than **mechanism**.
Instead of offering one very powerful feature of implicits, Scala 3 offers several use-case oriented features:
- **Retroactively extending classes**.
- In Scala 2, extension methods had to be encoded using implicit conversions or implicit classes.
+ In Scala 2, extension methods are encoded by using [implicit conversions][implicit-conversions] or [implicit classes]({% link _overviews/core/implicit-classes.md %}).
In contrast, in Scala 3 [extension methods][extension-methods] are now directly built into the language, leading to better error messages and improved type inference.
- **Abstracting over contextual information**.
@@ -46,11 +45,11 @@ Instead of offering one very powerful feature of implicits, Scala 3 offers sever
As an improvement over Scala 2 implicits, using clauses can be specified by type, freeing function signatures from term variable names that are never explicitly referred to.
- **Providing Type-class instances**.
- [Given instances][type-classes] allow programmers to define the _canonical value_ of a certain type.
- This makes programming with type-classes more straightforward without leaking implementation details.
+ [Given instances][givens] allow programmers to define the _canonical value_ of a certain type.
+ This makes programming with [type-classes][type-classes] more straightforward without leaking implementation details.
- **Viewing one type as another**.
- Implicit conversion have been [redesigned][implicit-conversions] from the ground up as instances of a type-class `Conversion`.
+ Implicit conversions have been [redesigned][implicit-conversions] from the ground up as instances of a type-class `Conversion`.
- **Higher-order contextual abstractions**.
The _all-new_ feature of [context functions][contextual-functions] makes contextual abstractions a first-class citizen.
@@ -78,11 +77,11 @@ Benefits of these changes include:
This chapter introduces many of these new features in the following sections.
-[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %}
+[givens]: {% link _overviews/scala3-book/ca-context-parameters.md %}
[given-imports]: {% link _overviews/scala3-book/ca-given-imports.md %}
[implicit-conversions]: {% link _overviews/scala3-book/ca-implicit-conversions.md %}
[extension-methods]: {% link _overviews/scala3-book/ca-extension-methods.md %}
[context-bounds]: {% link _overviews/scala3-book/ca-context-bounds.md %}
[type-classes]: {% link _overviews/scala3-book/ca-type-classes.md %}
[equality]: {% link _overviews/scala3-book/ca-multiversal-equality.md %}
-[contextual-functions]: {% link _overviews/scala3-book/types-dependent-function.md %}
+[contextual-functions]: {{ site.scala3ref }}/contextual/context-functions.html
diff --git a/_overviews/scala3-book/ca-extension-methods.md b/_overviews/scala3-book/ca-extension-methods.md
index 354bf8939e..6c3bcb735c 100644
--- a/_overviews/scala3-book/ca-extension-methods.md
+++ b/_overviews/scala3-book/ca-extension-methods.md
@@ -5,9 +5,14 @@ description: This page demonstrates how Extension Methods work in Scala 3.
languages: [zh-cn]
num: 59
previous-page: ca-contextual-abstractions-intro
-next-page: ca-given-using-clauses
+next-page: ca-context-parameters
+scala3: true
+versionSpecific: true
---
+In Scala 2, a similar result could be achieved with [implicit classes]({% link _overviews/core/implicit-classes.md %}).
+
+---
Extension methods let you add methods to a type after the type is defined, i.e., they let you add new methods to closed classes.
For example, imagine that someone else has created a `Circle` class:
diff --git a/_overviews/scala3-book/ca-given-imports.md b/_overviews/scala3-book/ca-given-imports.md
index b60e21f75d..37a5a9e626 100644
--- a/_overviews/scala3-book/ca-given-imports.md
+++ b/_overviews/scala3-book/ca-given-imports.md
@@ -6,17 +6,14 @@ languages: [zh-cn]
num: 62
previous-page: ca-context-bounds
next-page: ca-type-classes
+scala3: true
+versionSpecific: true
---
-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
@@ -28,28 +25,15 @@ 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
The wildcard selector `*` brings all definitions other than givens or extensions into scope, whereas a `given` selector brings all *givens*---including those resulting from extensions---into scope.
diff --git a/_overviews/scala3-book/ca-given-using-clauses.md b/_overviews/scala3-book/ca-given-using-clauses.md
deleted file mode 100644
index a0c747c450..0000000000
--- a/_overviews/scala3-book/ca-given-using-clauses.md
+++ /dev/null
@@ -1,147 +0,0 @@
----
-title: Given Instances and Using Clauses
-type: section
-description: This page demonstrates how to use 'given' instances and 'using' clauses in Scala 3.
-languages: [zh-cn]
-num: 60
-previous-page: ca-extension-methods
-next-page: ca-context-bounds
----
-
-
-
Use contextual abstraction Scala 3 Only
-
-Scala 3 offers two important feature for contextual abstraction:
-
-- **Using Clauses** allow you to specify parameters that, at the call site, can be omitted by the programmer and should be automatically provided by the context.
-- **Given Instances** let you define terms that can be used by the Scala compiler to fill in the missing arguments.
-
-## Using Clauses
-
-When designing a system, often context information like _configuration_ or settings need to be provided to the different components of your system.
-One common way to achieve this is by passing the configuration as additional argument to your methods.
-
-In the following example, we define a case class `Config` to model some website configuration and pass it around in the different methods.
-
-{% tabs nonusing %}
-{% tab 'Scala 2 and 3' %}
-
-```scala
-case class Config(port: Int, baseUrl: String)
-
-def renderWebsite(path: String, c: Config): String =
- "" + renderWidget(List("cart"), c) + ""
-
-def renderWidget(items: List[String], c: Config): String = ???
-
-val config = Config(8080, "docs.scala-lang.org")
-renderWebsite("/home", config)
-```
-
-{% endtab %}
-{% endtabs %}
-
-Let us assume that the configuration does not change throughout most of our code base.
-Passing `c` to each and every method call (like `renderWidget`) becomes very tedious and makes our program more difficult to read, since we need to ignore the `c` argument.
-
-#### Using `using` to mark parameters as contextual
-
-In Scala 3, we can mark some parameters of our methods as _contextual_.
-
-{% tabs using1 %}
-{% tab 'Scala 3 Only' %}
-
-```scala
-def renderWebsite(path: String)(using c: Config): String =
- "" + renderWidget(List("cart")) + ""
- // ^^^
- // no argument c required anymore
-
-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 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`.
-So the program is equivalent to the one above.
-
-In fact, since we do not need to refer to `c` in our implementation of `renderWebsite` anymore, we can even omit its name in the signature:
-
-{% tabs using2 %}
-{% tab 'Scala 3 Only' %}
-
-```scala
-// no need to come up with a parameter name
-// vvvvvvvvvvvvv
-def renderWebsite(path: String)(using Config): String =
- "" + renderWidget(List("cart")) + ""
-```
-
-{% endtab %}
-{% endtabs %}
-
-#### Explicitly providing contextual arguments
-
-We have seen how to _abstract_ over contextual parameters and that the Scala compiler can provide arguments automatically for us.
-But how can we specify which configuration to use for our call to `renderWebsite`?
-
-Like we specified our parameter section with `using`, we can also explicitly provide contextual arguments with `using:`
-
-{% tabs using3 %}
-{% tab 'Scala 3 Only' %}
-
-```scala
-renderWebsite("/home")(using config)
-```
-
-{% endtab %}
-{% endtabs %}
-
-Explicitly providing contextual parameters can be useful if we have multiple different values in scope that would make sense, and we want to make sure that the correct one is passed to the function.
-
-For all other cases, as we will see in the next Section, there is also another way to bring contextual values into scope.
-
-## Given Instances
-
-We have seen that we can explicitly pass arguments as contextual parameters by marking the argument section of the _call_ with `using`.
-However, if there is _a single canonical value_ for a particular type, there is another preferred way to make it available to the Scala compiler: by marking it as `given`.
-
-{% tabs given1 %}
-{% tab 'Scala 3 Only' %}
-
-```scala
-val config = Config(8080, "docs.scala-lang.org")
-// this is the type that we want to provide the
-// canonical value for
-// vvvvvv
-given Config = config
-// ^^^^^^
-// this is the value the Scala compiler will infer
-// as argument to contextual parameters of type Config
-```
-
-{% endtab %}
-{% endtabs %}
-
-In the above example we specify that whenever a contextual parameter of type `Config` is omitted in the current scope, the compiler should infer `config` as an argument.
-
-Having defined a given for `Config`, we can simply call `renderWebsite`:
-
-{% tabs given2 %}
-{% tab 'Scala 3 Only' %}
-
-```scala
-renderWebsite("/home")
-// ^^^^^
-// again no argument
-```
-
-{% endtab %}
-{% endtabs %}
-
-[reference]: {{ site.scala3ref }}/overview.html
-[blog-post]: /2020/11/06/explicit-term-inference-in-scala-3.html
diff --git a/_overviews/scala3-book/ca-implicit-conversions.md b/_overviews/scala3-book/ca-implicit-conversions.md
index 6294ffd64a..2318323b95 100644
--- a/_overviews/scala3-book/ca-implicit-conversions.md
+++ b/_overviews/scala3-book/ca-implicit-conversions.md
@@ -1,49 +1,223 @@
---
title: Implicit Conversions
type: section
-description: This page demonstrates how to implement Implicit Conversions in Scala 3.
+description: This page demonstrates how to implement Implicit Conversions in Scala.
languages: [zh-cn]
num: 65
previous-page: ca-multiversal-equality
next-page: ca-summary
---
+Implicit conversions are a powerful Scala feature that allows users to supply an argument
+of one type as if it were another type, to avoid boilerplate.
-Implicit conversions are defined by `given` instances of the `scala.Conversion` class.
-For example, not accounting for possible conversion errors, this code defines an implicit conversion from `String` to `Int`:
+> Note that in Scala 2, implicit conversions were also used to provide additional members
+> to closed classes (see [Implicit Classes]({% link _overviews/core/implicit-classes.md %})).
+> In Scala 3, we recommend to address this use-case by defining [extension methods] instead
+> of implicit conversions (although the standard library still relies on implicit conversions
+> for historical reasons).
+
+## Example
+
+Consider for instance a method `findUserById` that takes a parameter of type `Long`:
+
+{% tabs implicit-conversions-1 %}
+{% tab 'Scala 2 and 3' %}
+~~~ scala
+def findUserById(id: Long): Option[User]
+~~~
+{% endtab %}
+{% endtabs %}
+
+We omit the definition of the type `User` for the sake of brevity, it does not matter for
+our example.
+
+In Scala, it is possible to call the method `findUserById` with an argument of type `Int`
+instead of the expected type `Long`, because the argument will be implicitly converted
+into the type `Long`:
+
+{% tabs implicit-conversions-2 %}
+{% tab 'Scala 2 and 3' %}
+~~~ scala
+val id: Int = 42
+findUserById(id) // OK
+~~~
+{% endtab %}
+{% endtabs %}
+
+This code does not fail to compile with an error like “type mismatch: expected `Long`,
+found `Int`” because there is an implicit conversion that converts the argument `id`
+to a value of type `Long`.
+
+## Detailed Explanation
+
+This section describes how to define and use implicit conversions.
+
+### Defining an Implicit Conversion
+
+{% tabs implicit-conversions-3 class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+In Scala 2, an implicit conversion from type `S` to type `T` is defined by an
+[implicit class]({% link _overviews/core/implicit-classes.md %}) `T` that takes
+a single constructor parameter of type `S`, an
+[implicit value]({% link _overviews/scala3-book/ca-context-parameters.md %}) of
+function type `S => T`, or by an implicit method convertible to a value of that type.
+
+For example, the following code defines an implicit conversion from `Int` to `Long`:
+
+~~~ scala
+import scala.language.implicitConversions
+
+implicit def int2long(x: Int): Long = x.toLong
+~~~
+
+This is an implicit method convertible to a value of type `Int => Long`.
+
+See the section “Beware the Power of Implicit Conversions” below for an
+explanation of the clause `import scala.language.implicitConversions`
+at the beginning.
+{% endtab %}
+
+{% tab 'Scala 3' %}
+In Scala 3, an implicit conversion from type `S` to type `T` is defined by a
+[`given` instance]({% link _overviews/scala3-book/ca-context-parameters.md %})
+of type `scala.Conversion[S, T]`. For compatibility with Scala 2, it can also
+be defined by an implicit method (read more in the Scala 2 tab).
+
+For example, this code defines an implicit conversion from `Int` to `Long`:
```scala
-given Conversion[String, Int] with
- def apply(s: String): Int = Integer.parseInt(s)
+given int2long: Conversion[Int, Long] with
+ def apply(x: Int): Long = x.toLong
```
-Using an alias this can be expressed more concisely as:
+Like other given definitions, implicit conversions can be anonymous:
+
+~~~ scala
+given Conversion[Int, Long] with
+ def apply(x: Int): Long = x.toLong
+~~~
+
+Using an alias, this can be expressed more concisely as:
```scala
-given Conversion[String, Int] = Integer.parseInt(_)
+given Conversion[Int, Long] = (x: Int) => x.toLong
```
+{% endtab %}
-Using either of those conversions, you can now use a `String` in places where an `Int` is expected:
+{% endtabs %}
-```scala
+### Using an Implicit Conversion
+
+Implicit conversions are applied in two situations:
+
+1. If an expression `e` is of type `S`, and `S` does not conform to the expression's expected type `T`.
+2. In a selection `e.m` with `e` of type `S`, if the selector `m` does not denote a member of `S`
+ (to support Scala-2-style [extension methods]).
+
+In the first case, a conversion `c` is searched for, which is applicable to `e` and whose result type conforms to `T`.
+
+In our example above, when we pass the argument `id` of type `Int` to the method `findUserById`,
+the implicit conversion `int2long(id)` is inserted.
+
+In the second case, a conversion `c` is searched for, which is applicable to `e` and whose result contains a member named `m`.
+
+An example is to compare two strings `"foo" < "bar"`. In this case, `String` has no member `<`, so the implicit conversion `Predef.augmentString("foo") < "bar"` is inserted. (`scala.Predef` is automatically imported into all Scala programs.)
+
+### How Are Implicit Conversions Brought Into Scope?
+
+When the compiler searches for applicable conversions:
+
+- first, it looks into the current lexical scope
+ - implicit conversions defined in the current scope or the outer scopes
+ - imported implicit conversions
+ - implicit conversions imported by a wildcard import (Scala 2 only)
+- then, it looks into the [companion objects] _associated_ with the argument
+ type `S` or the expected type `T`. The companion objects associated with
+ a type `X` are:
+ - the companion object `X` itself
+ - the companion objects associated with any of `X`’s inherited types
+ - the companion objects associated with any type argument in `X`
+ - if `X` is an inner class, the outer objects in which it is embedded
+
+For instance, consider an implicit conversion `fromStringToUser` defined in an
+object `Conversions`:
+
+{% tabs implicit-conversions-4 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+~~~ scala
import scala.language.implicitConversions
-// a method that expects an Int
-def plus1(i: Int) = i + 1
+object Conversions {
+ implicit def fromStringToUser(name: String): User = (name: String) => User(name)
+}
+~~~
+{% endtab %}
+{% tab 'Scala 3' %}
+~~~ scala
+object Conversions:
+ given fromStringToUser: Conversion[String, User] = (name: String) => User(name)
+~~~
+{% endtab %}
+{% endtabs %}
-// pass it a String that converts to an Int
-plus1("1")
-```
+The following imports would equivalently bring the conversion into scope:
-> Note the clause `import scala.language.implicitConversions` at the beginning,
-> to enable implicit conversions in the file.
+{% tabs implicit-conversions-5 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+~~~ scala
+import Conversions.fromStringToUser
+// or
+import Conversions._
+~~~
+{% endtab %}
+{% tab 'Scala 3' %}
+~~~ scala
+import Conversions.fromStringToUser
+// or
+import Conversions.given
+// or
+import Conversions.{given Conversion[String, User]}
+~~~
-## Discussion
+Note that in Scala 3, a wildcard import (ie `import Conversions.*`) does not import given
+definitions.
+{% endtab %}
+{% endtabs %}
-The Predef package contains “auto-boxing” conversions that map primitive number types to subclasses of `java.lang.Number`.
-For instance, the conversion from `Int` to `java.lang.Integer` can be defined as follows:
+In the introductory example, the conversion from `Int` to `Long` does not require an import
+because it is defined in the object `Int`, which is the companion object of the type `Int`.
-```scala
-given int2Integer: Conversion[Int, java.lang.Integer] =
- java.lang.Integer.valueOf(_)
-```
+Further reading:
+[Where does Scala look for implicits? (on Stackoverflow)](https://stackoverflow.com/a/5598107).
+
+### Beware the Power of Implicit Conversions
+
+{% tabs implicit-conversions-6 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+Because implicit conversions can have pitfalls if used indiscriminately the compiler warns when compiling the implicit conversion definition.
+
+To turn off the warnings take either of these actions:
+
+* Import `scala.language.implicitConversions` into the scope of the implicit conversion definition
+* Invoke the compiler with `-language:implicitConversions`
+
+No warning is emitted when the conversion is applied by the compiler.
+{% endtab %}
+{% tab 'Scala 3' %}
+Because implicit conversions can have pitfalls if used indiscriminately the compiler warns in two situations:
+- when compiling a Scala 2 style implicit conversion definition.
+- at the call site where a given instance of `scala.Conversion` is inserted as a conversion.
+
+To turn off the warnings take either of these actions:
+
+- Import `scala.language.implicitConversions` into the scope of:
+ - a Scala 2 style implicit conversion definition
+ - call sites where a given instance of `scala.Conversion` is inserted as a conversion.
+- Invoke the compiler with `-language:implicitConversions`
+{% endtab %}
+{% endtabs %}
+
+[extension methods]: {% link _overviews/scala3-book/ca-extension-methods.md %}
+[companion objects]: {% link _overviews/scala3-book/domain-modeling-tools.md %}#companion-objects
diff --git a/_overviews/scala3-book/ca-multiversal-equality.md b/_overviews/scala3-book/ca-multiversal-equality.md
index 54aed55fa4..7d5d6d0c92 100644
--- a/_overviews/scala3-book/ca-multiversal-equality.md
+++ b/_overviews/scala3-book/ca-multiversal-equality.md
@@ -6,12 +6,9 @@ languages: [zh-cn]
num: 64
previous-page: ca-type-classes
next-page: ca-implicit-conversions
+scala3: true
+versionSpecific: true
---
-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.
diff --git a/_overviews/scala3-book/ca-summary.md b/_overviews/scala3-book/ca-summary.md
index ede541abe2..e3f3da3cad 100644
--- a/_overviews/scala3-book/ca-summary.md
+++ b/_overviews/scala3-book/ca-summary.md
@@ -10,16 +10,19 @@ next-page: concurrency
This chapter provides an introduction to most Contextual Abstractions topics, including:
-- Given Instances and Using Clauses
-- Context Bounds
-- Given Imports
-- Extension Methods
-- Implementing Type Classes
-- Multiversal Equality
-- Implicit Conversions
+- [Extension Methods]({% link _overviews/scala3-book/ca-extension-methods.md %})
+- [Given Instances and Using Clauses]({% link _overviews/scala3-book/ca-context-parameters.md %})
+- [Context Bounds]({% link _overviews/scala3-book/ca-context-bounds.md %})
+- [Given Imports]({% link _overviews/scala3-book/ca-given-imports.md %})
+- [Type Classes]({% link _overviews/scala3-book/ca-type-classes.md %})
+- [Multiversal Equality]({% link _overviews/scala3-book/ca-multiversal-equality.md %})
+- [Implicit Conversions]({% link _overviews/scala3-book/ca-implicit-conversions.md %})
+
+These features are all variants of the core idea of **term inference**: given a type, the compiler synthesizes a “canonical” term that has that type.
A few more advanced topics aren’t covered here, including:
+- Conditional Given Instances
- Type Class Derivation
- Context Functions
- By-Name Context Parameters
diff --git a/_overviews/scala3-book/ca-type-classes.md b/_overviews/scala3-book/ca-type-classes.md
index e0e81c8d50..18f5ef3f72 100644
--- a/_overviews/scala3-book/ca-type-classes.md
+++ b/_overviews/scala3-book/ca-type-classes.md
@@ -1,14 +1,14 @@
---
title: Type Classes
type: section
-description: This page demonstrates how to create and use type classes in Scala 3.
+description: This page demonstrates how to create and use type classes.
languages: [zh-cn]
num: 63
previous-page: ca-given-imports
next-page: ca-multiversal-equality
+redirect_from: /scala3/book/types-type-classes.html
---
-
A _type class_ is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing.
If you are coming from Java, you can think of type classes as something like [`java.util.Comparator[T]`][comparator].
@@ -20,69 +20,115 @@ A type class is useful in multiple use-cases, for example:
- Expressing how a type you don’t own---from the standard library or a third-party library---conforms to such behavior
- Expressing such a behavior for multiple types without involving sub-typing relationships between those types
-In Scala 3, type classes are just traits with one or more parameters whose implementations are provided by `given` instances.
-
-
+Type classes are traits with one or more parameters whose implementations are provided as `given` instances in Scala 3 or `implicit` values in Scala 2.
## Example
-For example, `Show` is a well-known type class in Haskell, and the following code shows one way to implement it in Scala 3.
-If you imagine that Scala classes don’t have a `toString` method, you can define a `Show` type class to add this behavior to any class that you want to be able to convert to a custom string.
+For example, `Show` is a well-known type class in Haskell, and the following code shows one way to implement it in Scala.
+If you imagine that Scala classes don’t have a `toString` method, you can define a `Show` type class to add this behavior to any type that you want to be able to convert to a custom string.
### The type class
The first step in creating a type class is to declare a parameterized trait that has one or more abstract methods.
Because `Showable` only has one method named `show`, it’s written like this:
+{% tabs 'definition' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+// a type class
+trait Showable[A] {
+ def show(a: A): String
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
```scala
// a type class
trait Showable[A]:
- extension(a: A) def show: String
+ extension (a: A) def show: String
```
+{% endtab %}
+{% endtabs %}
-This is the Scala 3 way of saying that any type that implements this trait must define how the `show` method works.
-Notice that the syntax is very close to a normal trait:
+Notice that this approach is close to the usual object-oriented approach, where you would typically define a trait `Show` as follows:
+{% tabs 'trait' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+// a trait
+trait Show {
+ def show: String
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
```scala
// a trait
trait Show:
def show: String
```
+{% endtab %}
+{% endtabs %}
There are a few important things to point out:
-1. Type-classes like `Showable` take a type parameter `A` to say which type we provide the implementation of `show` for; in contrast, normal traits like `Show` do not.
-2. To add the show functionality to a certain type `A`, the normal trait requires that `A extends Show`, while for type-classes we require to have an implementation of `Showable[A]`.
-3. To allow the same method calling syntax in both `Showable` that mimics the one of `Show`, we define `Showable.show` as an extension method.
+1. Type-classes like `Showable` take a type parameter `A` to say which type we provide the implementation of `show` for; in contrast, classic traits like `Show` do not.
+2. To add the show functionality to a certain type `A`, the classic trait requires that `A extends Show`, while for type-classes we require to have an implementation of `Showable[A]`.
+3. In Scala 3, to allow the same method calling syntax in both `Showable` that mimics the one of `Show`, we define `Showable.show` as an extension method.
### Implement concrete instances
The next step is to determine what classes in your application `Showable` should work for, and then implement that behavior for them.
For instance, to implement `Showable` for this `Person` class:
+{% tabs 'person' %}
+{% tab 'Scala 2 and 3' %}
```scala
case class Person(firstName: String, lastName: String)
```
+{% endtab %}
+{% endtabs %}
-you’ll define a `given` value for `Showable[Person]`.
-This code provides a concrete instance of `Showable` for the `Person` class:
+you’ll define a single _canonical value_ of type `Showable[Person]`, ie an instance of `Showable` for the type `Person`, as the following code example demonstrates:
+{% tabs 'instance' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+implicit val showablePerson: Showable[Person] = new Showable[Person] {
+ def show(p: Person): String =
+ s"${p.firstName} ${p.lastName}"
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
```scala
given Showable[Person] with
- extension(p: Person) def show: String =
+ extension (p: Person) def show: String =
s"${p.firstName} ${p.lastName}"
```
-
-As shown, this is defined as an extension method on the `Person` class, and it uses the reference `p` inside the body of the `show` method.
+{% endtab %}
+{% endtabs %}
### Using the type class
Now you can use this type class like this:
+{% tabs 'usage' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+val person = Person("John", "Doe")
+println(showablePerson.show(person))
+```
+
+Not that in practice, type classes are typically used with values whose type is unknown, unlike the type `Person`, as shown in the next section.
+{% endtab %}
+{% tab 'Scala 3' %}
```scala
val person = Person("John", "Doe")
println(person.show)
```
+{% endtab %}
+{% endtabs %}
Again, if Scala didn’t have a `toString` method available to every class, you could use this technique to add `Showable` behavior to any class that you want to be able to convert to a `String`.
@@ -90,29 +136,52 @@ Again, if Scala didn’t have a `toString` method available to every class, you
As with inheritance, you can define methods that use `Showable` as a type parameter:
+{% tabs 'method' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+def showAll[A](as: List[A])(implicit showable: Showable[A]): Unit =
+ as.foreach(a => println(showable.show(a)))
+
+showAll(List(Person("Jane"), Person("Mary")))
+```
+{% endtab %}
+{% tab 'Scala 3' %}
```scala
-def showAll[S: Showable](xs: List[S]): Unit =
- xs.foreach(x => println(x.show))
+def showAll[A: Showable](as: List[A]): Unit =
+ as.foreach(a => println(a.show))
showAll(List(Person("Jane"), Person("Mary")))
```
+{% endtab %}
+{% endtabs %}
### A type class with multiple methods
Note that if you want to create a type class that has multiple methods, the initial syntax looks like this:
+{% tabs 'multiple-methods' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+trait HasLegs[A] {
+ def walk(a: A): Unit
+ def run(a: A): Unit
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
```scala
trait HasLegs[A]:
extension (a: A)
def walk(): Unit
def run(): Unit
```
+{% endtab %}
+{% endtabs %}
### A real-world example
For a real-world example of how type classes are used in Scala 3, see the `CanEqual` discussion in the [Multiversal Equality section][multiversal].
-
[typeclasses-paper]: https://infoscience.epfl.ch/record/150280/files/TypeClasses.pdf
[typeclasses-chapter]: {% link _overviews/scala3-book/ca-type-classes.md %}
[comparator]: https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html
diff --git a/_overviews/scala3-book/collections-classes.md b/_overviews/scala3-book/collections-classes.md
index b9ba20afc8..c68bc9e035 100644
--- a/_overviews/scala3-book/collections-classes.md
+++ b/_overviews/scala3-book/collections-classes.md
@@ -2,7 +2,7 @@
title: Collections Types
type: section
description: This page introduces the common Scala 3 collections types and some of their methods.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 37
previous-page: collections-intro
next-page: collections-methods
diff --git a/_overviews/scala3-book/collections-intro.md b/_overviews/scala3-book/collections-intro.md
index 8a0ae572ba..befedef0e6 100644
--- a/_overviews/scala3-book/collections-intro.md
+++ b/_overviews/scala3-book/collections-intro.md
@@ -2,7 +2,7 @@
title: Scala Collections
type: chapter
description: This page provides and introduction to the common collections classes and their methods in Scala 3.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 36
previous-page: packaging-imports
next-page: collections-classes
diff --git a/_overviews/scala3-book/collections-methods.md b/_overviews/scala3-book/collections-methods.md
index 35fcb72b70..f165a2c0e9 100644
--- a/_overviews/scala3-book/collections-methods.md
+++ b/_overviews/scala3-book/collections-methods.md
@@ -2,7 +2,7 @@
title: Collections Methods
type: section
description: This page demonstrates the common methods on the Scala 3 collections classes.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 38
previous-page: collections-classes
next-page: collections-summary
diff --git a/_overviews/scala3-book/collections-summary.md b/_overviews/scala3-book/collections-summary.md
index 446c8b90c9..c8ac5512fa 100644
--- a/_overviews/scala3-book/collections-summary.md
+++ b/_overviews/scala3-book/collections-summary.md
@@ -2,7 +2,7 @@
title: Summary
type: section
description: This page provides a summary of the Collections chapter.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 39
previous-page: collections-methods
next-page: fp-intro
diff --git a/_overviews/scala3-book/domain-modeling-fp.md b/_overviews/scala3-book/domain-modeling-fp.md
index 6942cd124c..f8ab2fb571 100644
--- a/_overviews/scala3-book/domain-modeling-fp.md
+++ b/_overviews/scala3-book/domain-modeling-fp.md
@@ -35,7 +35,7 @@ Ignoring the division of whole numbers, the possible *operations* on those value
+, -, *
````
-An FP design is implemented in a similar way:
+In FP, business domains are modeled in a similar way:
- You describe your set of values (your data)
- You describe operations that work on those values (your functions)
diff --git a/_overviews/scala3-book/fp-functional-error-handling.md b/_overviews/scala3-book/fp-functional-error-handling.md
index edc5f8a5ed..829845eaf8 100644
--- a/_overviews/scala3-book/fp-functional-error-handling.md
+++ b/_overviews/scala3-book/fp-functional-error-handling.md
@@ -2,7 +2,7 @@
title: Functional Error Handling
type: section
description: This section provides an introduction to functional error handling in Scala 3.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 45
previous-page: fp-functions-are-values
next-page: fp-summary
diff --git a/_overviews/scala3-book/fp-functions-are-values.md b/_overviews/scala3-book/fp-functions-are-values.md
index b4df001cce..48f7e4ca3a 100644
--- a/_overviews/scala3-book/fp-functions-are-values.md
+++ b/_overviews/scala3-book/fp-functions-are-values.md
@@ -2,7 +2,7 @@
title: Functions Are Values
type: section
description: This section looks at the use of functions as values in functional programming.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 44
previous-page: fp-pure-functions
next-page: fp-functional-error-handling
diff --git a/_overviews/scala3-book/fp-immutable-values.md b/_overviews/scala3-book/fp-immutable-values.md
index 6202943906..413d2d0495 100644
--- a/_overviews/scala3-book/fp-immutable-values.md
+++ b/_overviews/scala3-book/fp-immutable-values.md
@@ -2,7 +2,7 @@
title: Immutable Values
type: section
description: This section looks at the use of immutable values in functional programming.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 42
previous-page: fp-what-is-fp
next-page: fp-pure-functions
diff --git a/_overviews/scala3-book/fp-intro.md b/_overviews/scala3-book/fp-intro.md
index 56c43362c8..7d6e6f4114 100644
--- a/_overviews/scala3-book/fp-intro.md
+++ b/_overviews/scala3-book/fp-intro.md
@@ -2,7 +2,7 @@
title: Functional Programming
type: chapter
description: This chapter provides an introduction to functional programming in Scala 3.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 40
previous-page: collections-summary
next-page: fp-what-is-fp
diff --git a/_overviews/scala3-book/fp-pure-functions.md b/_overviews/scala3-book/fp-pure-functions.md
index fd7c356421..754d6f46a5 100644
--- a/_overviews/scala3-book/fp-pure-functions.md
+++ b/_overviews/scala3-book/fp-pure-functions.md
@@ -2,7 +2,7 @@
title: Pure Functions
type: section
description: This section looks at the use of pure functions in functional programming.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 43
previous-page: fp-immutable-values
next-page: fp-functions-are-values
diff --git a/_overviews/scala3-book/fp-summary.md b/_overviews/scala3-book/fp-summary.md
index 7a9caff971..55233f851c 100644
--- a/_overviews/scala3-book/fp-summary.md
+++ b/_overviews/scala3-book/fp-summary.md
@@ -2,7 +2,7 @@
title: Summary
type: section
description: This section summarizes the previous functional programming sections.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 46
previous-page: fp-functional-error-handling
next-page: types-introduction
diff --git a/_overviews/scala3-book/fp-what-is-fp.md b/_overviews/scala3-book/fp-what-is-fp.md
index 034f2ecc16..1e37279ae0 100644
--- a/_overviews/scala3-book/fp-what-is-fp.md
+++ b/_overviews/scala3-book/fp-what-is-fp.md
@@ -2,7 +2,7 @@
title: What is Functional Programming?
type: section
description: This section provides an answer to the question, what is functional programming?
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 41
previous-page: fp-intro
next-page: fp-immutable-values
diff --git a/_overviews/scala3-book/fun-hofs.md b/_overviews/scala3-book/fun-hofs.md
index 90ebba7396..45d764dee4 100644
--- a/_overviews/scala3-book/fun-hofs.md
+++ b/_overviews/scala3-book/fun-hofs.md
@@ -15,8 +15,6 @@ In Scala, HOFs are possible because functions are first-class values.
As an important note, while we use the common industry term “higher-order function” in this document, in Scala this phrase applies to both *methods* and *functions*.
Thanks to Scala’s [Eta Expansion technology][eta_expansion], they can generally be used in the same places.
-
-
## From consumer to creator
In the examples so far in this book you’ve seen how to be a *consumer* of methods that take other functions as input parameters, such as using HOFs like `map` and `filter`.
@@ -32,8 +30,6 @@ In the process you’ll see:
As a beneficial side effect of this discussion, once you’re comfortable with this syntax, you’ll use it to define function parameters, anonymous functions, and function variables, and it also becomes easier to read the Scaladoc for higher-order functions.
-
-
## Understanding filter’s Scaladoc
To understand how higher-order functions work, it helps to dig into an example.
@@ -56,21 +52,27 @@ At this point, if you don’t know the purpose of the `filter` method, all you
Looking specifically at the function parameter `p`, this part of `filter`’s description:
+{% tabs filter-definition_1 %}
+{% tab 'Scala 2 and 3' %}
```scala
p: A => Boolean
```
+{% endtab %}
+{% endtabs %}
means that whatever function you pass in must take the type `A` as an input parameter and return a `Boolean`.
So if your list is a `List[Int]`, you can replace the generic type `A` with `Int`, and read that signature like this:
+{% tabs filter-definition_2 %}
+{% tab 'Scala 2 and 3' %}
```scala
p: Int => Boolean
```
+{% endtab %}
+{% endtabs %}
Because `isEven` has this type---it transforms an input `Int` into a resulting `Boolean`---it can be used with `filter`.
-
-
{% comment %}
NOTE: (A low-priority issue): The next several sections can be condensed.
{% endcomment %}
@@ -101,9 +103,13 @@ def sayHello(f: () => Unit): Unit = f()
This portion of the code---the *type signature*---states that `f` is a function, and defines the types of functions the `sayHello` method will accept:
+{% tabs sayHello-definition_1 %}
+{% tab 'Scala 2 and 3' %}
```scala
f: () => Unit
```
+{% endtab %}
+{% endtabs %}
Here’s how this works:
@@ -125,7 +131,6 @@ def helloJoe(): Unit = println("Hello, Joe")
{% endtab %}
{% endtabs %}
-
Because the type signatures match, you can pass `helloJoe` into `sayHello`:
{% tabs sayHello-usage %}
@@ -139,7 +144,6 @@ sayHello(helloJoe) // prints "Hello, Joe"
If you’ve never done this before, congratulations:
You just defined a method named `sayHello` that takes a function as an input parameter, and then invokes that function in its method body.
-
### sayHello can take many functions
It’s important to know that the beauty of this approach is not that `sayHello` can take *one* function as an input parameter; the beauty is that it can take *any* function that matches `f`’s signature.
@@ -167,8 +171,6 @@ Bonjour, Julien
This is a good start.
The only thing to do now is see a few more examples of how to define different type signatures for function parameters.
-
-
## The general syntax for defining function input parameters
In this method:
@@ -183,26 +185,38 @@ def sayHello(f: () => Unit): Unit
We noted that the type signature for `f` is:
+{% tabs sayHello-definition-2_1 %}
+{% tab 'Scala 2 and 3' %}
```scala
() => Unit
```
+{% endtab %}
+{% endtabs %}
We know that this means, “a function that takes no input parameters and returns nothing meaningful (given by `Unit`).”
To demonstrate more type signature examples, here’s a function that takes a `String` parameter and returns an `Int`:
+{% tabs sayHello-definition-2_2 %}
+{% tab 'Scala 2 and 3' %}
```scala
f: String => Int
```
+{% endtab %}
+{% endtabs %}
What kinds of functions take a string and return an integer?
Functions like “string length” and checksum are two examples.
Similarly, this function takes two `Int` parameters and returns an `Int`:
+{% tabs sayHello-definition-2_3 %}
+{% tab 'Scala 2 and 3' %}
```scala
f: (Int, Int) => Int
```
+{% endtab %}
+{% endtabs %}
Can you imagine what sort of functions match that signature?
@@ -220,15 +234,17 @@ def multiply(a: Int, b: Int): Int = a * b
As you can infer from these examples, the general syntax for defining function parameter type signatures is:
+{% tabs add-sub-mul-definitions_1 %}
+{% tab 'Scala 2 and 3' %}
```scala
variableName: (parameterTypes ...) => returnType
```
+{% endtab %}
+{% endtabs %}
> Because functional programming is like creating and combining a series of algebraic equations, it’s common to think about types a *lot* when designing functions and applications.
> You might say that you “think in types.”
-
-
## Taking a function parameter along with other parameters
For HOFs to be really useful, they also need some data to work on.
@@ -279,11 +295,8 @@ Hello, world
{% endtab %}
{% endtabs %}
-
Excellent.
The `executeNTimes` method executes the `helloWorld` function three times.
-
-
### As many parameters as needed
Your methods can continue to get as complicated as necessary.
@@ -298,7 +311,6 @@ def executeAndPrint(f: (Int, Int) => Int, i: Int, j: Int): Unit =
{% endtab %}
{% endtabs %}
-
Because these `sum` and `multiply` methods match that type signature, they can be passed into `executeAndPrint` along with two `Int` values:
{% tabs executeAndPrint-usage %}
@@ -313,7 +325,6 @@ executeAndPrint(multiply, 3, 9) // prints 27
{% endtab %}
{% endtabs %}
-
## Function type signature consistency
A great thing about learning about Scala’s function type signatures is that the syntax you use to define function input parameters is the same syntax you use to write function literals.
diff --git a/_overviews/scala3-book/methods-main-methods.md b/_overviews/scala3-book/methods-main-methods.md
index 3415ef3496..407c124d2c 100644
--- a/_overviews/scala3-book/methods-main-methods.md
+++ b/_overviews/scala3-book/methods-main-methods.md
@@ -72,7 +72,7 @@ Happy 23rd Birthday, Lisa and Peter!
```
As shown, the `@main` method can have an arbitrary number of parameters.
-For each parameter type there must be a [given instance](./ca-given-using-clauses.html) of the `scala.util.CommandLineParser.FromString` type class that converts an argument `String` to the required parameter type.
+For each parameter type there must be a [given instance]({% link _overviews/scala3-book/ca-context-parameters.md %}) of the `scala.util.CommandLineParser.FromString` type class that converts an argument `String` to the required parameter type.
Also as shown, a main method’s parameter list can end in a repeated parameter like `String*` that takes all remaining arguments given on the command line.
The program implemented from an `@main` method checks that there are enough arguments on the command line to fill in all parameters, and that the argument strings can be converted to the required types.
diff --git a/_overviews/scala3-book/methods-most.md b/_overviews/scala3-book/methods-most.md
index a1d17c318e..1cadaf3ebe 100644
--- a/_overviews/scala3-book/methods-most.md
+++ b/_overviews/scala3-book/methods-most.md
@@ -469,6 +469,7 @@ This makes them private to the current class, so they can’t be called nor over
{% tabs method_20 class=tabs-scala-version %}
{% tab 'Scala 2' for=method_20 %}
+
```scala
class Animal {
private def breathe() = println("I’m breathing")
diff --git a/_overviews/scala3-book/packaging-imports.md b/_overviews/scala3-book/packaging-imports.md
index 6cd3835078..562fc69a46 100644
--- a/_overviews/scala3-book/packaging-imports.md
+++ b/_overviews/scala3-book/packaging-imports.md
@@ -2,7 +2,7 @@
title: Packaging and Imports
type: chapter
description: A discussion of using packages and imports to organize your code, build related modules of code, control scope, and help prevent namespace collisions.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 35
previous-page: fun-summary
next-page: collections-intro
@@ -21,24 +21,25 @@ With Scala you can:
These features are demonstrated in the following examples.
-
-
## Creating a package
Packages are created by declaring one or more package names at the top of a Scala file.
For example, when your domain name is _acme.com_ and you’re working in the _model_ package of an application named _myapp_, your package declaration looks like this:
+{% tabs packaging-imports-1 %}
+{% tab 'Scala 2 and 3' %}
```scala
package com.acme.myapp.model
class Person ...
```
+{% endtab %}
+{% endtabs %}
By convention, package names should be all lower case, and the formal naming convention is *\.\.\.\*.
Although it’s not required, package names typically follow directory structure names, so if you follow this convention, a `Person` class in this project will be found in a *MyApp/src/main/scala/com/acme/myapp/model/Person.scala* file.
-
### Using multiple packages in the same file
The syntax shown above applies to the entire source file: all the definitions in the file
@@ -48,6 +49,22 @@ at the beginning of the file.
Alternatively, it is possible to write package clauses that apply only to the definitions
they contain:
+{% tabs packaging-imports-1 class=tabs-scala-version %}
+{% tab 'Scala 2' %}```scala
+package users {
+
+ package administrators { // the full name of this package is users.administrators
+ class AdminUser // the full name of this class users.administrators.AdminUser
+ }
+ package normalusers { // the full name of this package is users.normalusers
+ class NormalUser // the full name of this class is users.normalusers.NormalUser
+ }
+}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=packaging-imports-1 %}
+
```scala
package users:
@@ -57,14 +74,14 @@ package users:
package normalusers: // the full name of this package is users.normalusers
class NormalUser // the full name of this class is users.normalusers.NormalUser
```
+{% endtab %}
+{% endtabs %}
Note that the package names are followed by a colon, and that the definitions within
a package are indented.
The advantages of this approach are that it allows for package nesting, and provides more obvious control of scope and encapsulation, especially within the same file.
-
-
## Import statements, Part 1
Import statements are used to access entities in other packages.
@@ -76,12 +93,27 @@ Import statements fall into two main categories:
If you’re used to a language like Java, the first class of import statements is similar to what Java uses, with a slightly different syntax that allows for more flexibility.
These examples demonstrate some of that flexibility:
-````
+{% tabs packaging-imports-2 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+import users._ // import everything from the `users` package
+import users.User // import only the `User` class
+import users.{User, UserPreferences} // import only two selected members
+import users.{UserPreferences => UPrefs} // rename a member as you import it
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=packaging-imports-2 %}
+
+```scala
import users.* // import everything from the `users` package
import users.User // import only the `User` class
import users.{User, UserPreferences} // import only two selected members
import users.{UserPreferences as UPrefs} // rename a member as you import it
-````
+```
+
+{% endtab %}
+{% endtabs %}
Those examples are meant to give you a taste of how the first class of `import` statements work.
They’re explained more in the subsections that follow.
@@ -93,90 +125,165 @@ A note before moving on:
> Import clauses are not required for accessing members of the same package.
-
-
### Importing one or more members
In Scala you can import one member from a package like this:
+{% tabs packaging-imports-3 %}
+{% tab 'Scala 2 and 3' %}
```scala
import scala.concurrent.Future
```
+{% endtab %}
+{% endtabs %}
and multiple members like this:
+{% tabs packaging-imports-4 %}
+{% tab 'Scala 2 and 3' %}
```scala
import scala.concurrent.Future
import scala.concurrent.Promise
import scala.concurrent.blocking
```
+{% endtab %}
+{% endtabs %}
When importing multiple members, you can import them more concisely like this:
+{% tabs packaging-imports-5 %}
+{% tab 'Scala 2 and 3' %}
```scala
import scala.concurrent.{Future, Promise, blocking}
```
+{% endtab %}
+{% endtabs %}
When you want to import everything from the *scala.concurrent* package, use this syntax:
+{% tabs packaging-imports-6 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+import scala.concurrent._
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=packaging-imports-6 %}
+
```scala
import scala.concurrent.*
```
+{% endtab %}
+{% endtabs %}
### Renaming members on import
Sometimes it can help to rename entities when you import them to avoid name collisions.
For instance, if you want to use the Scala `List` class and also the *java.util.List* class at the same time, you can rename the *java.util.List* class when you import it:
+{% tabs packaging-imports-7 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+import java.util.{List => JavaList}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=packaging-imports-7 %}
+
```scala
import java.util.{List as JavaList}
```
+{% endtab %}
+{% endtabs %}
Now you use the name `JavaList` to refer to that class, and use `List` to refer to the Scala list class.
You can also rename multiple members at one time using this syntax:
+{% tabs packaging-imports-8 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+import java.util.{Date => JDate, HashMap => JHashMap, _}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=packaging-imports-8 %}
+
```scala
import java.util.{Date as JDate, HashMap as JHashMap, *}
```
-That line of code says, “Rename the `Date` and `HashMap` classes as shown, and import everything else in the _java.util_ package without renaming any other members.”
+{% endtab %}
+{% endtabs %}
+That line of code says, “Rename the `Date` and `HashMap` classes as shown, and import everything else in the _java.util_ package without renaming any other members.”
### Hiding members on import
You can also *hide* members during the import process.
This `import` statement hides the *java.util.Random* class, while importing everything else in the *java.util* package:
+{% tabs packaging-imports-9 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+import java.util.{Random => _, _}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=packaging-imports-9 %}
+
```scala
import java.util.{Random as _, *}
```
+{% endtab %}
+{% endtabs %}
If you try to access the `Random` class it won’t work, but you can access all other members from that package:
+{% tabs packaging-imports-10 %}
+{% tab 'Scala 2 and 3' %}
```scala
val r = new Random // won’t compile
new ArrayList // works
```
+{% endtab %}
+{% endtabs %}
#### Hiding multiple members
To hide multiple members during the import process, list them before using the final wildcard import:
+{% tabs packaging-imports-11 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+import java.util.{List => _, Map => _, Set => _, _}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=packaging-imports-11 %}
+
```scala
scala> import java.util.{List as _, Map as _, Set as _, *}
```
+{% endtab %}
+{% endtabs %}
Once again those classes are hidden, but you can use all other classes in *java.util*:
+{% tabs packaging-imports-12 %}
+{% tab 'Scala 2 and 3' %}
```scala
scala> new ArrayList[String]
val res0: java.util.ArrayList[String] = []
```
+{% endtab %}
+{% endtabs %}
Because those Java classes are hidden, you can also use the Scala `List`, `Set`, and `Map` classes without having a naming collision:
+{% tabs packaging-imports-13 %}
+{% tab 'Scala 2 and 3' %}
```scala
scala> val a = List(1, 2, 3)
val a: List[Int] = List(1, 2, 3)
@@ -187,13 +294,32 @@ val b: Set[Int] = Set(1, 2, 3)
scala> val c = Map(1 -> 1, 2 -> 2)
val c: Map[Int, Int] = Map(1 -> 1, 2 -> 2)
```
-
+{% endtab %}
+{% endtabs %}
### Use imports anywhere
In Scala, `import` statements can be anywhere.
They can be used at the top of a source code file:
+{% tabs packaging-imports-14 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+package foo
+
+import scala.util.Random
+
+class ClassA {
+ def printRandom(): Unit = {
+ val r = new Random // use the imported class
+ // more code here...
+ }
+}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=packaging-imports-14 %}
+
```scala
package foo
@@ -204,9 +330,33 @@ class ClassA:
val r = new Random // use the imported class
// more code here...
```
+{% endtab %}
+{% endtabs %}
You can also use `import` statements closer to the point where they are needed, if you prefer:
+{% tabs packaging-imports-15 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+package foo
+
+class ClassA {
+ import scala.util.Random // inside ClassA
+ def printRandom(): Unit = {
+ val r = new Random
+ // more code here...
+ }
+}
+
+class ClassB {
+ // the Random class is not visible here
+ val r = new Random // this code will not compile
+}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=packaging-imports-15 %}
+
```scala
package foo
@@ -221,6 +371,8 @@ class ClassB:
val r = new Random // this code will not compile
```
+{% endtab %}
+{% endtabs %}
### “Static” imports
@@ -228,19 +380,43 @@ When you want to import members in a way similar to the Java “static import”
Use this syntax to import all static members of the Java `Math` class:
+{% tabs packaging-imports-16 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+import java.lang.Math._
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=packaging-imports-16 %}
+
```scala
import java.lang.Math.*
```
+{% endtab %}
+{% endtabs %}
Now you can access static `Math` class methods like `sin` and `cos` without having to precede them with the class name:
+{% tabs packaging-imports-17 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
```scala
-import java.lang.Math.*
+import java.lang.Math._
val a = sin(0) // 0.0
val b = cos(PI) // -1.0
```
+{% endtab %}
+{% tab 'Scala 3' for=packaging-imports-17 %}
+
+```scala
+import java.lang.Math.*
+
+val a = sin(0) // 0.0
+val b = cos(PI) // -1.0
+```
+{% endtab %}
+{% endtabs %}
### Packages imported by default
@@ -253,25 +429,36 @@ The members of the Scala object `Predef` are also imported by default.
> If you ever wondered why you can use classes like `List`, `Vector`, `Map`, etc., without importing them, they’re available because of definitions in the `Predef` object.
-
-
### Handling naming conflicts
In the rare event there’s a naming conflict and you need to import something from the root of the project, prefix the package name with `_root_`:
-```
+{% tabs packaging-imports-18 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
package accounts
-import _root_.accounts.*
+import _root_.accounts._
```
+{% endtab %}
+{% tab 'Scala 3' for=packaging-imports-18 %}
+
+```scala
+package accounts
+import _root_.accounts.*
+```
+{% endtab %}
+{% endtabs %}
## Importing `given` instances
-As you’ll see in the [Contextual Abstractions][contextual] chapter, a special form of the `import` statement is used to import `given` instances.
+As you’ll see in the [Contextual Abstractions][contextual] chapter, in Scala 3 a special form of the `import` statement is used to import `given` instances.
The basic form is shown in this example:
+{% tabs packaging-imports-19 %}
+{% tab 'Scala 3 only' %}
```scala
object A:
class TC
@@ -282,16 +469,22 @@ object B:
import A.* // import all non-given members
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 packaging-imports-20 %}
+{% tab 'Scala 3 only' %}
```scala
object B:
import A.{given, *}
```
-
+{% endtab %}
+{% endtabs %}
+In Scala 2, that style of import does not exist. Implicit definitions are always imported by the wildcard import.
### Discussion
The wildcard selector `*` brings all definitions other than givens or extensions into scope, whereas a `given` selector brings all *givens*---including those resulting from extensions---into scope.
@@ -303,26 +496,35 @@ These rules have two main benefits:
- It enables importing all givens without importing anything else.
This is particularly important since givens can be anonymous, so the usual use of named imports is not practical.
-
### By-type imports
Since givens can be anonymous, it’s not always practical to import them by their name, and wildcard imports are typically used instead.
*By-type imports* provide a more specific alternative to wildcard imports, which makes it more clear what is imported:
+{% tabs packaging-imports-21 %}
+{% tab 'Scala 3 only' %}
```scala
import A.{given TC}
```
+{% endtab %}
+{% endtabs %}
This imports any `given` in `A` that has a type which conforms to `TC`.
Importing givens of several types `T1,...,Tn` is expressed by multiple `given` selectors:
+{% tabs packaging-imports-22 %}
+{% tab 'Scala 3 only' %}
```scala
import A.{given T1, ..., given Tn}
```
+{% endtab %}
+{% endtabs %}
Importing all `given` instances of a parameterized type is expressed by wildcard arguments.
For example, when you have this `object`:
+{% tabs packaging-imports-23 %}
+{% tab 'Scala 3 only' %}
```scala
object Instances:
given intOrd as Ordering[Int]
@@ -330,26 +532,38 @@ object Instances:
given ec as ExecutionContext = ...
given im as Monoid[Int]
```
+{% endtab %}
+{% endtabs %}
This import statement imports the `intOrd`, `listOrd`, and `ec` instances, but leaves out the `im` instance because it doesn’t fit any of the specified bounds:
+{% tabs packaging-imports-24 %}
+{% tab 'Scala 3 only' %}
```scala
import Instances.{given Ordering[?], given ExecutionContext}
```
+{% endtab %}
+{% endtabs %}
By-type imports can be mixed with by-name imports.
If both are present in an import clause, by-type imports come last.
For instance, this import clause imports `im`, `intOrd`, and `listOrd`, but leaves out `ec`:
+{% tabs packaging-imports-25 %}
+{% tab 'Scala 3 only' %}
```scala
import Instances.{im, given Ordering[?]}
```
-
+{% endtab %}
+{% endtabs %}
### An example
As a concrete example, imagine that you have this `MonthConversions` object that contains two `given` definitions:
+{% tabs packaging-imports-26 %}
+{% tab 'Scala 3 only' %}
+
```scala
object MonthConversions:
trait MonthConverter[A]:
@@ -369,31 +583,46 @@ object MonthConversions:
case "feb" => "February"
// more cases here ...
```
+{% endtab %}
+{% endtabs %}
To import those givens into the current scope, use these two `import` statements:
+{% tabs packaging-imports-27 %}
+{% tab 'Scala 3 only' %}
+
```scala
import MonthConversions.*
import MonthConversions.{given MonthConverter[?]}
```
+{% endtab %}
+{% endtabs %}
Now you can create a method that uses those `given` instances:
+{% tabs packaging-imports-28 %}
+{% tab 'Scala 3 only' %}
+
```scala
def genericMonthConverter[A](a: A)(using monthConverter: MonthConverter[A]): String =
monthConverter.convert(a)
```
+{% endtab %}
+{% endtabs %}
Then you can use that method in your application:
+{% tabs packaging-imports-29 %}
+{% tab 'Scala 3 only' %}
+
```scala
@main def main =
println(genericMonthConverter(1)) // January
println(genericMonthConverter("jan")) // January
```
+{% endtab %}
+{% endtabs %}
As mentioned, one of the key design benefits of the “import given” syntax is to make it clear where givens in scope come from, and it’s clear in these `import` statements that the givens come from the `MonthConversions` object.
-
-
[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %}
diff --git a/_overviews/scala3-book/scala-features.md b/_overviews/scala3-book/scala-features.md
index 0e5b94f0db..d6f72d1873 100644
--- a/_overviews/scala3-book/scala-features.md
+++ b/_overviews/scala3-book/scala-features.md
@@ -244,7 +244,7 @@ In particular, the type system supports:
- [Intersection types]({% link _overviews/scala3-book/types-intersection.md %})
- [Union types]({% link _overviews/scala3-book/types-union.md %})
- [Type lambdas]({{ site.scala3ref }}/new-types/type-lambdas.html)
-- [`given` instances and `using` clauses]({% link _overviews/scala3-book/ca-given-using-clauses.md %})
+- [`given` instances and `using` clauses]({% link _overviews/scala3-book/ca-context-parameters.md %})
- [Extension methods]({% link _overviews/scala3-book/ca-extension-methods.md %})
- [Type classes]({% link _overviews/scala3-book/ca-type-classes.md %})
- [Multiversal equality]({% link _overviews/scala3-book/ca-multiversal-equality.md %})
@@ -572,7 +572,7 @@ As this page shows, Scala has many terrific programming language features at a h
[reference]: {{ site.scala3ref }}/overview.html
[multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %}
[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %}
-[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %}
+[givens]: {% link _overviews/scala3-book/ca-context-parameters.md %}
[opaque_types]: {% link _overviews/scala3-book/types-opaque-types.md %}
diff --git a/_overviews/scala3-book/scala-for-java-devs.md b/_overviews/scala3-book/scala-for-java-devs.md
index 61b2d558d2..b6c707f038 100644
--- a/_overviews/scala3-book/scala-for-java-devs.md
+++ b/_overviews/scala3-book/scala-for-java-devs.md
@@ -1306,7 +1306,7 @@ This includes:
[equality]: {% link _overviews/scala3-book/ca-multiversal-equality.md %}
[error-handling]: {% link _overviews/scala3-book/fp-functional-error-handling.md %}
[extension-methods]: {% link _overviews/scala3-book/ca-extension-methods.md %}
-[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %}
+[givens]: {% link _overviews/scala3-book/ca-context-parameters.md %}
[hofs]: {% link _overviews/scala3-book/fun-hofs.md %}
[imports]: {% link _overviews/scala3-book/packaging-imports.md %}
[modeling-intro]: {% link _overviews/scala3-book/domain-modeling-intro.md %}
diff --git a/_overviews/scala3-book/scala-for-javascript-devs.md b/_overviews/scala3-book/scala-for-javascript-devs.md
index 478a0b028d..f2ecd821e4 100644
--- a/_overviews/scala3-book/scala-for-javascript-devs.md
+++ b/_overviews/scala3-book/scala-for-javascript-devs.md
@@ -1366,7 +1366,7 @@ There are other concepts in Scala which currently have no equivalent in JavaScri
[control]: {% link _overviews/scala3-book/control-structures.md %}
[extension-methods]: {% link _overviews/scala3-book/ca-extension-methods.md %}
[fp-intro]: {% link _overviews/scala3-book/fp-intro.md %}
-[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %}
+[givens]: {% link _overviews/scala3-book/ca-context-parameters.md %}
[hofs]: {% link _overviews/scala3-book/fun-hofs.md %}
[intersection-types]: {% link _overviews/scala3-book/types-intersection.md %}
[modeling-fp]: {% link _overviews/scala3-book/domain-modeling-fp.md %}
diff --git a/_overviews/scala3-book/types-adts-gadts.md b/_overviews/scala3-book/types-adts-gadts.md
index 1ee8fb48a6..5cb2d0e550 100644
--- a/_overviews/scala3-book/types-adts-gadts.md
+++ b/_overviews/scala3-book/types-adts-gadts.md
@@ -6,6 +6,8 @@ languages: [zh-cn]
num: 52
previous-page: types-union
next-page: types-variance
+scala3: true
+versionSpecific: true
---
diff --git a/_overviews/scala3-book/types-generics.md b/_overviews/scala3-book/types-generics.md
index c175158c55..c44c29c9ac 100644
--- a/_overviews/scala3-book/types-generics.md
+++ b/_overviews/scala3-book/types-generics.md
@@ -2,7 +2,7 @@
title: Generics
type: section
description: This section introduces and demonstrates generics in Scala 3.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 49
previous-page: types-inferred
next-page: types-intersection
@@ -63,7 +63,7 @@ This is how you create and use a `Stack[Int]`:
{% tabs stack-usage class=tabs-scala-version %}
{% tab 'Scala 2' %}
-```
+```scala
val stack = new Stack[Int]
stack.push(1)
stack.push(2)
@@ -71,8 +71,9 @@ println(stack.pop()) // prints 2
println(stack.pop()) // prints 1
```
{% endtab %}
+
{% tab 'Scala 3' %}
-```
+```scala
val stack = Stack[Int]
stack.push(1)
stack.push(2)
diff --git a/_overviews/scala3-book/types-inferred.md b/_overviews/scala3-book/types-inferred.md
index 733a8d540c..828907d048 100644
--- a/_overviews/scala3-book/types-inferred.md
+++ b/_overviews/scala3-book/types-inferred.md
@@ -2,7 +2,7 @@
title: Inferred Types
type: section
description: This section introduces and demonstrates inferred types in Scala 3
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 48
previous-page: types-introduction
next-page: types-generics
diff --git a/_overviews/scala3-book/types-intersection.md b/_overviews/scala3-book/types-intersection.md
index 9336b7dd68..8c01dd9b33 100644
--- a/_overviews/scala3-book/types-intersection.md
+++ b/_overviews/scala3-book/types-intersection.md
@@ -41,10 +41,23 @@ Therefore, as shown, `Resettable & Growable[String]` has member methods `reset`
Intersection types can be useful to describe requirements _structurally_.
That is, in our example `f`, we directly express that we are happy with any value for `x` as long as it’s a subtype of both `Resettable` and `Growable`.
We **did not** have to create a _nominal_ helper trait like the following:
+
+{% tabs normal-trait class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+trait Both[A] extends Resettable with Growable[A]
+def f(x: Both[String]): Unit
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
```scala
trait Both[A] extends Resettable, Growable[A]
def f(x: Both[String]): Unit
```
+{% endtab %}
+{% endtabs %}
+
There is an important difference between the two alternatives of defining `f`: While both allow `f` to be called with instances of `Both`, only the former allows passing instances that are subtypes of `Resettable` and `Growable[String]`, but _not of_ `Both[String]`.
> Note that `&` is _commutative_: `A & B` is the same type as `B & A`.
diff --git a/_overviews/scala3-book/types-introduction.md b/_overviews/scala3-book/types-introduction.md
index f2fe4c245e..f5267d7eb2 100644
--- a/_overviews/scala3-book/types-introduction.md
+++ b/_overviews/scala3-book/types-introduction.md
@@ -2,7 +2,7 @@
title: Types and the Type System
type: chapter
description: This chapter provides an introduction to Scala 3 types and the type system.
-languages: [zh-cn]
+languages: [ru, zh-cn]
num: 47
previous-page: fp-summary
next-page: types-inferred
diff --git a/_overviews/scala3-book/types-union.md b/_overviews/scala3-book/types-union.md
index 1636556767..271c9a0148 100644
--- a/_overviews/scala3-book/types-union.md
+++ b/_overviews/scala3-book/types-union.md
@@ -6,6 +6,8 @@ languages: [zh-cn]
num: 51
previous-page: types-intersection
next-page: types-adts-gadts
+scala3: true
+versionSpecific: true
---
Used on types, the `|` operator creates a so-called _union type_.
@@ -43,7 +45,7 @@ case 1.0 => ??? // ERROR: this line won’t compile
As shown, union types can be used to represent alternatives of several different types, without requiring those types to be part of a custom-crafted class hierarchy, or requiring explicit wrapping.
#### Pre-planning the Class Hierarchy
-Other languages would require pre-planning of the class hierarchy, like the following example illustrates:
+Without union types, it would require pre-planning of the class hierarchy, like the following example illustrates:
```scala
trait UsernameOrPassword
@@ -51,6 +53,7 @@ case class Username(name: String) extends UsernameOrPassword
case class Password(hash: Hash) extends UsernameOrPassword
def help(id: UsernameOrPassword) = ...
```
+
Pre-planning does not scale very well since, for example, requirements of API users might not be foreseeable.
Additionally, cluttering the type hierarchy with marker traits like `UsernameOrPassword` also makes the code more difficult to read.
diff --git a/_overviews/scala3-book/types-variance.md b/_overviews/scala3-book/types-variance.md
index 568ce0e494..9fd69a2802 100644
--- a/_overviews/scala3-book/types-variance.md
+++ b/_overviews/scala3-book/types-variance.md
@@ -11,13 +11,41 @@ next-page: types-opaque-types
Type parameter _variance_ controls the subtyping of parameterized types (like classes or traits).
To explain variance, let us assume the following type definitions:
+
+{% tabs types-variance-1 %}
+{% tab 'Scala 2 and 3' %}
```scala
trait Item { def productNumber: String }
trait Buyable extends Item { def price: Int }
trait Book extends Buyable { def isbn: String }
+
```
+{% endtab %}
+{% endtabs %}
Let us also assume the following parameterized types:
+
+{% tabs types-variance-2 class=tabs-scala-version %}
+{% tab 'Scala 2' for=types-variance-2 %}
+```scala
+// an example of an invariant type
+trait Pipeline[T] {
+ def process(t: T): T
+}
+
+// an example of a covariant type
+trait Producer[+T] {
+ def make: T
+}
+
+// an example of a contravariant type
+trait Consumer[-T] {
+ def take(t: T): Unit
+}
+```
+{% endtab %}
+
+{% tab 'Scala 3' for=types-variance-2 %}
```scala
// an example of an invariant type
trait Pipeline[T]:
@@ -31,6 +59,9 @@ trait Producer[+T]:
trait Consumer[-T]:
def take(t: T): Unit
```
+{% endtab %}
+{% endtabs %}
+
In general there are three modes of variance:
- **invariant**---the default, written like `Pipeline[T]`
@@ -45,6 +76,22 @@ This means that types like `Pipeline[Item]`, `Pipeline[Buyable]`, and `Pipeline[
And rightfully so! Assume the following method that consumes two values of type `Pipeline[Buyable]`, and passes its argument `b` to one of them, based on the price:
+{% tabs types-variance-3 class=tabs-scala-version %}
+{% tab 'Scala 2' for=types-variance-3 %}
+```scala
+def oneOf(
+ p1: Pipeline[Buyable],
+ p2: Pipeline[Buyable],
+ b: Buyable
+): Buyable = {
+ val b1 = p1.process(b)
+ val b2 = p2.process(b)
+ if (b1.price < b2.price) b1 else b2
+ }
+```
+{% endtab %}
+
+{% tab 'Scala 3' for=types-variance-3 %}
```scala
def oneOf(
p1: Pipeline[Buyable],
@@ -55,10 +102,19 @@ def oneOf(
val b2 = p2.process(b)
if b1.price < b2.price then b1 else b2
```
+{% endtab %}
+{% endtabs %}
+
Now, recall that we have the following _subtyping relationship_ between our types:
+
+{% tabs types-variance-4 %}
+{% tab 'Scala 2 and 3' %}
```scala
Book <: Buyable <: Item
```
+{% endtab %}
+{% endtabs %}
+
We cannot pass a `Pipeline[Book]` to the method `oneOf` because in its implementation, we call `p1` and `p2` with a value of type `Buyable`.
A `Pipeline[Book]` expects a `Book`, which can potentially cause a runtime error.
@@ -68,7 +124,6 @@ We cannot pass a `Pipeline[Item]` because calling `process` on it only promises
In fact, type `Pipeline` needs to be invariant since it uses its type parameter `T` _both_ as an argument _and_ as a return type.
For the same reason, some types in the Scala collection library---like `Array` or `Set`---are also _invariant_.
-
### Covariant Types
In contrast to `Pipeline`, which is invariant, the type `Producer` is marked as **covariant** by prefixing the type parameter with a `+`.
This is valid, since the type parameter is only used in a _return position_.
@@ -78,33 +133,47 @@ And in fact, this is sound. The type of `Producer[Buyable].make` only promises t
As a caller of `make`, we will be happy to also accept a `Book`, which is a subtype of `Buyable`---that is, it is _at least_ a `Buyable`.
This is illustrated by the following example, where the function `makeTwo` expects a `Producer[Buyable]`:
+
+{% tabs types-variance-5 %}
+{% tab 'Scala 2 and 3' %}
```scala
def makeTwo(p: Producer[Buyable]): Int =
p.make.price + p.make.price
```
+{% endtab %}
+{% endtabs %}
+
It is perfectly fine to pass a producer for books:
-```
+
+{% tabs types-variance-6 %}
+{% tab 'Scala 2 and 3' %}
+```scala
val bookProducer: Producer[Book] = ???
makeTwo(bookProducer)
```
-The call to `price` within `makeTwo` is still valid also for books.
+{% endtab %}
+{% endtabs %}
+The call to `price` within `makeTwo` is still valid also for books.
#### Covariant Types for Immutable Containers
You will encounter covariant types a lot when dealing with immutable containers, like those that can be found in the standard library (such as `List`, `Seq`, `Vector`, etc.).
For example, `List` and `Vector` are approximately defined as:
+{% tabs types-variance-7 %}
+{% tab 'Scala 2 and 3' %}
```scala
class List[+A] ...
class Vector[+A] ...
```
+{% endtab %}
+{% endtabs %}
This way, you can use a `List[Book]` where a `List[Buyable]` is expected.
This also intuitively makes sense: If you are expecting a collection of things that can be bought, it should be fine to give you a collection of books.
They have an additional ISBN method in our example, but you are free to ignore these additional capabilities.
-
### Contravariant Types
In contrast to the type `Producer`, which is marked as covariant, the type `Consumer` is marked as **contravariant** by prefixing the type parameter with a `-`.
This is valid, since the type parameter is only used in an _argument position_.
@@ -116,20 +185,34 @@ Remember, for type `Producer`, it was the other way around, and we had `Producer
And in fact, this is sound. The method `Consumer[Item].take` accepts an `Item`.
As a caller of `take`, we can also supply a `Buyable`, which will be happily accepted by the `Consumer[Item]` since `Buyable` is a subtype of `Item`---that is, it is _at least_ an `Item`.
-
#### Contravariant Types for Consumers
Contravariant types are much less common than covariant types.
As in our example, you can think of them as “consumers.” The most important type that you might come across that is marked contravariant is the one of functions:
+{% tabs types-variance-8 class=tabs-scala-version %}
+{% tab 'Scala 2' for=types-variance-8 %}
+```scala
+trait Function[-A, +B] {
+ def apply(a: A): B
+}
+```
+{% endtab %}
+
+{% tab 'Scala 3' for=types-variance-8 %}
```scala
trait Function[-A, +B]:
def apply(a: A): B
```
+{% endtab %}
+{% endtabs %}
+
Its argument type `A` is marked as contravariant `A`---it consumes values of type `A`.
In contrast, its result type `B` is marked as covariant---it produces values of type `B`.
Here are some examples that illustrate the subtyping relationships induced by variance annotations on functions:
+{% tabs types-variance-9 %}
+{% tab 'Scala 2 and 3' %}
```scala
val f: Function[Buyable, Buyable] = b => b
@@ -139,6 +222,8 @@ val g: Function[Buyable, Item] = f
// OK to provide a Book where a Buyable is expected
val h: Function[Book, Buyable] = f
```
+{% endtab %}
+{% endtabs %}
## Summary
In this section, we have encountered three different kinds of variance:
diff --git a/_overviews/scala3-book/why-scala-3.md b/_overviews/scala3-book/why-scala-3.md
index 44b14845d5..639c04691e 100644
--- a/_overviews/scala3-book/why-scala-3.md
+++ b/_overviews/scala3-book/why-scala-3.md
@@ -490,7 +490,7 @@ Several surveys have shown that different groups of developers love different fe
Hopefully you’ll discover more great Scala features as you use the language.
[java]: {% link _overviews/scala3-book/interacting-with-java.md %}
-[given]: {% link _overviews/scala3-book/ca-given-using-clauses.md %}
+[given]: {% link _overviews/scala3-book/ca-context-parameters.md %}
[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %}
[reference]: {{ site.scala3ref }}
[dropped]: {{ site.scala3ref }}/dropped-features
diff --git a/_overviews/scala3-contribution/procedures-debugging.md b/_overviews/scala3-contribution/procedures-debugging.md
index c57ff07591..d9411040af 100644
--- a/_overviews/scala3-contribution/procedures-debugging.md
+++ b/_overviews/scala3-contribution/procedures-debugging.md
@@ -56,23 +56,22 @@ First You can give a `name` to your configuration, for instance `Debug Scala 3 C
The two most important parameters, to debug the compiler, are `mainClass` and `args`.
The `mainClass` of the compiler is `dotty.tools.dotc.Main`.
-In the `args` you need to specify the compiler arguments, which must contain at least a `.scala` file to compile and a `-classpath` option.
+In the `args` you need to specify the compiler arguments, which must contain at least a Scala file to compile and a `-classpath` option.
To start with, we can compile the `../tests/pos/HelloWorld.scala` file.
In the classpath, we always need at least the `scala-library_2.13` and the bootstrapped `scala3-library_3`.
-To locate them on your filesystem you can run the `show scala3-library-bootstrapped/fullClasspath` command in sbt.
+To locate them on your filesystem you can run the `export scala3-library-bootstrapped/fullClasspath` command in sbt.
```
$ sbt
-> show scala3-library-bootstrapped/fullClasspath
-[info] * Attributed(/home/user/lampepfl/dotty/out/bootstrap/scala3-library-bootstrapped/scala-3.3.1-RC1-bin-SNAPSHOT-nonbootstrapped/classes)
-[info] * Attributed(/home/user/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.10/scala-library-2.13.10.jar)
+> export scala3-library-bootstrapped/fullClasspath
+/home/user/lampepfl/dotty/out/bootstrap/scala3-library-bootstrapped/scala-3.3.1-RC1-bin-SNAPSHOT-nonbootstrapped/classes:/home/user/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.10/scala-library-2.13.10.jar
[success] Total time: 1 s, completed Mar 10, 2023, 4:37:43 PM
```
Note that it is important to use the bootstrapped version of the `scala3-library` to get the correct TASTy version.
-Additionally you can add the `["-color", "never"]` arguments to prevent the compiler from printing ANSI codes as strings in the debug console.
+Additionally you can add the `-color` and `never` arguments to prevent the compiler from printing ANSI codes as strings in the debug console.
Here is the final configuration:
```json
@@ -84,7 +83,7 @@ Here is the final configuration:
"args": [
"../tests/pos/HelloWorld.scala",
"-classpath",
- // to replace with your own paths
+ // To replace with your own paths
"/home/user/lampepfl/dotty/out/bootstrap/scala3-library-bootstrapped/scala-3.3.1-RC1-bin-SNAPSHOT-nonbootstrapped/classes:/home/user/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.10/scala-library-2.13.10.jar",
"-color",
"never"
diff --git a/_overviews/scala3-contribution/procedures-inspection.md b/_overviews/scala3-contribution/procedures-inspection.md
index 6876882e41..891f3ee5eb 100644
--- a/_overviews/scala3-contribution/procedures-inspection.md
+++ b/_overviews/scala3-contribution/procedures-inspection.md
@@ -13,7 +13,9 @@ while the compiler is running, and inspect produced artifacts of the compiler.
## Inspecting variables in-place
Frequently you will need to inspect the content of a particular variable.
-Often, it is sufficient to use `println`.
+You can either use `println`s or the debugger, more info on how to setup the latter [here]({% link _overviews/scala3-contribution/procedures-debugging.md %}).
+
+In the remeainder of this article we'll use `println()` inserted in the code, but the same effect can be accomplished by stopping at a breakpoint, and typing `` in the [debug console]({% link _overviews/scala3-contribution/procedures-debugging.md %}#the-debug-console) of the debugger.
When printing a variable, it's always a good idea to call `show` on that variable: `println(x.show)`.
Many objects of the compiler define `show`, returning a human-readable string.
diff --git a/_overviews/scala3-contribution/start-intro.md b/_overviews/scala3-contribution/start-intro.md
index 4a2e111699..1ad97dc2f6 100644
--- a/_overviews/scala3-contribution/start-intro.md
+++ b/_overviews/scala3-contribution/start-intro.md
@@ -11,7 +11,7 @@ next-page: procedures-intro
### Scala CLA
-Sometime before submitting you're pull request you'll want to make sure you have
+Sometime before submitting your pull request you'll want to make sure you have
signed the [Scala CLA][scala-cla]. You can read more about why we require a CLA
and what exactly is included in it [here][scala-cla].
@@ -21,7 +21,7 @@ Before digging into an issue or starting on a new feature it's a good idea to
make sure an [issue][dotty-issue] or a [discussion][dotty-discussion] has been
created outlining what you plan to work on. This is both for your and the team's
benefit. It ensures you get the help you need, and also gives the compiler team
-a heads up that someone is working on an issue.
+a heads-up that someone is working on an issue.
For some small changes like documentation, this isn't always necessary, but it's
never a bad idea to check.
diff --git a/_overviews/scala3-migration/incompat-contextual-abstractions.md b/_overviews/scala3-migration/incompat-contextual-abstractions.md
index cd43809c28..ba88ed62f0 100644
--- a/_overviews/scala3-migration/incompat-contextual-abstractions.md
+++ b/_overviews/scala3-migration/incompat-contextual-abstractions.md
@@ -27,25 +27,31 @@ The Scalafix rule named `ExplicitImplicitTypes` in [ohze/scala-rewrites](https:/
Scala 3 does not support implicit conversion from an implicit function value, of the form `implicit val ev: A => B`.
-The following piece of code is now invalid:
+{% tabs scala-2-implicit_1 %}
+{% tab 'Scala 2 Only' %}
-```scala
+The following piece of code is now invalid in Scala 3:
+~~~ scala
trait Pretty {
val print: String
}
def pretty[A](a: A)(implicit ev: A => Pretty): String =
- a.print // Error: value print is not a member of A
-```
+ a.print // In Scala 3, Error: value print is not a member of A
+~~~
+{% endtab %}
+{% endtabs %}
The [Scala 3 migration compilation](tooling-migration-mode.html) can warn you about those cases, but it does not try to fix it.
Be aware that this incompatibility can produce a runtime incompatibility and break your program.
Indeed the compiler can find another implicit conversion from a broader scope, which would eventually cause an undesired behavior at runtime.
-This example illustrates the case:
+{% tabs shared-implicit_2 %}
+{% tab 'Scala 2 and 3' %}
-```scala
+This example illustrates the case:
+~~~ scala
trait Pretty {
val print: String
}
@@ -54,13 +60,15 @@ implicit def anyPretty(any: Any): Pretty = new Pretty { val print = "any" }
def pretty[A](a: A)(implicit ev: A => Pretty): String =
a.print // always print "any"
-```
+~~~
+{% endtab %}
+{% endtabs %}
The resolved conversion depends on the compiler mode:
- `-source:3.0-migration`: the compiler performs the `ev` conversion
- `-source:3.0`: the compiler cannot perform the `ev` conversion but it can perform the `anyPretty`, which is undesired
-One simple fix is to supply the right conversion explicitly:
+In Scala 3, one simple fix is to supply the right conversion explicitly:
{% highlight diff %}
def pretty[A](a: A)(implicit ev: A => Pretty): String =
@@ -73,11 +81,15 @@ def pretty[A](a: A)(implicit ev: A => Pretty): String =
View bounds have been deprecated for a long time but they are still supported in Scala 2.13.
They cannot be compiled with Scala 3 anymore.
-```scala
+{% tabs scala-2-bounds_1 %}
+{% tab 'Scala 2 Only' %}
+~~~ scala
def foo[A <% Long](a: A): Long = a
-```
+~~~
+{% endtab %}
+{% endtabs %}
-In this example we get:
+In this example, in Scala 3, we get this following error message:
{% highlight text %}
-- Error: src/main/scala/view-bound.scala:2:12
@@ -104,12 +116,16 @@ It is not the case in Scala 3 anymore, and leads to an ambiguous conversion.
For instance, in this example:
-```scala
+{% tabs scala-2-ambiguous_1 %}
+{% tab 'Scala 2 Only' %}
+~~~ scala
implicit def boolFoo(bool: Boolean): Foo = ???
implicit def lazyBoolFoo(lazyBool: => Boolean): Foo = ???
true.foo()
-```
+~~~
+{% endtab %}
+{% endtabs %}
The Scala 2.13 compiler chooses the `boolFoo` conversion but the Scala 3 compiler fails to compile.
@@ -131,4 +147,4 @@ implicit def lazyBoolFoo(lazyBool: => Boolean): Foo = ???
-true.foo()
+boolFoo(true).foo()
-{% endhighlight %}
+{% endhighlight %}
\ No newline at end of file
diff --git a/_overviews/scala3-migration/incompat-dropped-features.md b/_overviews/scala3-migration/incompat-dropped-features.md
index ae7e3e3f1e..4d5b2d5d73 100644
--- a/_overviews/scala3-migration/incompat-dropped-features.md
+++ b/_overviews/scala3-migration/incompat-dropped-features.md
@@ -19,6 +19,7 @@ Most of these changes can be handled automatically during the [Scala 3 migration
|[`any2stringadd` conversion](#any2stringadd-conversion)|Deprecation||[✅](https://github.com/scala/scala-rewrites/blob/main/rewrites/src/main/scala/fix/scala213/Any2StringAdd.scala)|
|[Early initializer](#early-initializer)|Deprecation|||
|[Existential type](#existential-type)|Feature warning|||
+|[@specialized](#specialized)|Deprecation|||
## Symbol literals
@@ -256,3 +257,12 @@ def foo: Bar
Note that using a wildcard argument, `_` or `?`, is often simpler but is not always possible.
For instance you could replace `List[T] forSome { type T }` by `List[?]`.
+
+## Specialized
+
+The `@specialized` annotation from Scala 2 is ignored in Scala 3.
+
+However, there is limited support for specialized `Function` and `Tuple`.
+
+Similar benefits can be derived from `inline` declarations.
+
diff --git a/_overviews/scala3-scaladoc/static-site.md b/_overviews/scala3-scaladoc/static-site.md
index 4eda6f88da..12377e8901 100644
--- a/_overviews/scala3-scaladoc/static-site.md
+++ b/_overviews/scala3-scaladoc/static-site.md
@@ -70,6 +70,12 @@ Scaladoc uses the [Liquid](https://shopify.github.io/liquid/) templating engine
and provides several custom filters and tags specific to Scala
documentation.
+The following project related variables are available and can be accessed using
+double curly braces (e.g. `{{ projectTitle }}`):
+
+- **projectTitle** the project title defined with the `-project` flag.
+- **projectVersion** the project version defined with the `-project-version` flag.
+
In Scaladoc, all templates can contain YAML front-matter. The front-matter
is parsed and put into the `page` variable available in templates via Liquid.
diff --git a/_overviews/toolkit/OrderedListOfMdFiles b/_overviews/toolkit/OrderedListOfMdFiles
new file mode 100644
index 0000000000..ea248772fe
--- /dev/null
+++ b/_overviews/toolkit/OrderedListOfMdFiles
@@ -0,0 +1,29 @@
+introduction.md
+testing-intro.md
+testing-suite.md
+testing-run.md
+testing-run-only.md
+testing-exceptions.md
+testing-asynchronous.md
+testing-resources.md
+testing-what-else.md
+os-intro.md
+os-read-directory.md
+os-read-file.md
+os-write-file.md
+os-run-process.md
+os-what-else.md
+json-intro.md
+json-parse.md
+json-modify.md
+json-deserialize.md
+json-serialize.md
+json-files.md
+json-what-else.md
+http-client-intro.md
+http-client-request.md
+http-client-uris.md
+http-client-request-body.md
+http-client-json.md
+http-client-upload-file.md
+http-client-what-else.md
diff --git a/_overviews/toolkit/http-client-intro.md b/_overviews/toolkit/http-client-intro.md
new file mode 100644
index 0000000000..fd2e132c54
--- /dev/null
+++ b/_overviews/toolkit/http-client-intro.md
@@ -0,0 +1,20 @@
+---
+title: Sending HTTP requests with sttp
+type: chapter
+description: The introduction of the sttp library
+num: 23
+previous-page: json-what-else
+next-page: http-client-request
+---
+
+sttp is a popular and feature-rich library for making HTTP requests to web servers.
+
+It provides both a synchronous API and an asynchronous `Future`-based API. It also supports WebSockets.
+
+Extensions are available that add capabilities such as streaming, logging, telemetry, and serialization.
+
+sttp offers the same APIs on all platforms (JVM, Scala.js, and Scala Native).
+
+sttp is a good choice for small synchronous scripts as well as large-scale, highly concurrent, asynchronous applications.
+
+{% include markdown.html path="_markdown/install-sttp.md" %}
diff --git a/_overviews/toolkit/http-client-json.md b/_overviews/toolkit/http-client-json.md
new file mode 100644
index 0000000000..4fca18f91f
--- /dev/null
+++ b/_overviews/toolkit/http-client-json.md
@@ -0,0 +1,128 @@
+---
+title: How to send and receive JSON?
+type: section
+description: How to send JSON in a request and to parse JSON from the response.
+num: 27
+previous-page: http-client-request-body
+next-page: http-client-upload-file
+---
+
+{% include markdown.html path="_markdown/install-sttp.md" %}
+
+## HTTP and JSON
+
+JSON is a common format for HTTP request and response bodies.
+
+In the examples below, we use the [GitHub REST API](https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28).
+You will need a secret [GitHub authentication token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) to run the programs.
+Do not share your token with anyone.
+
+## Sending and receiving JSON
+
+To send a JSON request and parse a JSON response we use sttp in combination with uJson.
+
+### Sending JSON
+
+To send JSON, you can construct a `uJson.Value` and write it as a string in the body of the request.
+
+In the following example we use [GitHub users endpoint](https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28) to update the profile of the authenticated user.
+We provide the new location and bio of the profile in a JSON object.
+
+{% tabs 'json' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc:compile-only
+import sttp.client4.quick._
+
+val json = ujson.Obj(
+ "location" -> "hometown",
+ "bio" -> "Scala programmer"
+)
+
+val response = quickRequest
+ .patch(uri"https://api.github.com/user")
+ .auth.bearer(sys.env("GITHUB_TOKEN"))
+ .header("Content-Type", "application/json")
+ .body(ujson.write(json))
+ .send()
+
+println(response.code)
+// prints: 200
+
+println(response.body)
+// prints the full updated profile in JSON
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import sttp.client4.quick.*
+
+val json = ujson.Obj(
+ "location" -> "hometown",
+ "bio" -> "Scala programmer"
+)
+
+val response = quickRequest
+ .patch(uri"https://api.github.com/user")
+ .auth.bearer(sys.env("GITHUB_TOKEN"))
+ .header("Content-Type", "application/json")
+ .body(ujson.write(json))
+ .send()
+
+println(response.code)
+// prints: 200
+
+println(response.body)
+// prints the full updated profile in JSON
+```
+{% endtab %}
+{% endtabs %}
+
+Before running the program, set the `GITHUB_TOKEN` environment variable.
+After running it, you should see your new bio and location on your GitHub profile.
+
+### Parsing JSON from the response
+
+To parse JSON from the response of a request, you can use `ujson.read`.
+
+Again we use the GitHub user endpoint, this time to get the authenticated user.
+
+{% tabs 'json-2' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc:compile-only
+import sttp.client4.quick._
+
+val response = quickRequest
+ .get(uri"https://api.github.com/user")
+ .auth.bearer(sys.env("GITHUB_TOKEN"))
+ .send()
+
+val json = ujson.read(response.body)
+
+println(json("login").str)
+// prints your login
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import sttp.client4.quick.*
+
+val response = quickRequest
+ .get(uri"https://api.github.com/user")
+ .auth.bearer(sys.env("GITHUB_TOKEN"))
+ .send()
+
+val json = ujson.read(response.body)
+
+println(json("login").str)
+// prints your login
+```
+{% endtab %}
+{% endtabs %}
+
+Before running the program, set the `GITHUB_TOKEN` environment variable.
+Running the program should print your own login.
+
+## Sending and receiving Scala objects using JSON
+
+Alternatively, you can use uPickle to send or receive Scala objects using JSON.
+Read the following to learn [*How to serialize an object to JSON*](/toolkit/json-serialize) and [*How to deserialize JSON to an object*](/toolkit/json-deserialize).
diff --git a/_overviews/toolkit/http-client-request-body.md b/_overviews/toolkit/http-client-request-body.md
new file mode 100644
index 0000000000..b54357e1cb
--- /dev/null
+++ b/_overviews/toolkit/http-client-request-body.md
@@ -0,0 +1,61 @@
+---
+title: How to send a request with a body?
+type: section
+description: Sending a string body with sttp
+num: 26
+previous-page: http-client-uris
+next-page: http-client-json
+---
+
+{% include markdown.html path="_markdown/install-sttp.md" %}
+
+## Sending a request with a string body
+
+To send a POST request with a string body, you can chain `post` and `body` on a `quickRequest`:
+{% tabs 'body' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc
+import sttp.client4.quick._
+
+val response = quickRequest
+ .post(uri"https://example.com/")
+ .body("Lorem ipsum")
+ .send()
+
+println(response.code)
+// prints: 200
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import sttp.client4.quick.*
+
+val response = quickRequest
+ .post(uri"https://example.com/")
+ .body("Lorem ipsum")
+ .send()
+
+println(response.code)
+// prints: 200
+```
+{% endtab %}
+{% endtabs %}
+
+In a request with string body, sttp adds the `Content-Type: text/plain; charset=utf-8` header and computes the `Content-Length` header.
+
+## Binary data
+
+The `body` method can also take a `Array[Byte]`, a `ByteBuffer` or an `InputStream`.
+
+{% tabs 'binarydata' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+val bytes: Array[Byte] = "john".getBytes
+val request = quickRequest.post(uri"https://example.com/").body(bytes)
+```
+{% endtab %}
+{% endtabs %}
+
+The binary body of a request is sent with `Content-Type: application/octet-stream`.
+
+Learn more in the [sttp documentation chapter about request bodies](https://sttp.softwaremill.com/en/latest/requests/body.html).
diff --git a/_overviews/toolkit/http-client-request.md b/_overviews/toolkit/http-client-request.md
new file mode 100644
index 0000000000..3a63e29c45
--- /dev/null
+++ b/_overviews/toolkit/http-client-request.md
@@ -0,0 +1,123 @@
+---
+title: How to send a request?
+type: section
+description: Sending a simple HTTP request with sttp.
+num: 24
+previous-page: http-client-intro
+next-page: http-client-uris
+---
+
+{% include markdown.html path="_markdown/install-sttp.md" %}
+
+## Sending an HTTP request
+
+The simplest way to send a request with sttp is `quickRequest`.
+
+You can define a GET request with `.get` and send it with `.send`.
+
+{% tabs 'request' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc
+import sttp.client4.quick._
+import sttp.client4.Response
+
+val response: Response[String] = quickRequest
+ .get(uri"https://httpbin.org/get")
+ .send()
+
+println(response.code)
+// prints: 200
+
+println(response.body)
+// prints some JSON string
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import sttp.client4.quick.*
+import sttp.client4.Response
+
+val response: Response[String] = quickRequest
+ .get(uri"https://httpbin.org/get")
+ .send()
+
+println(response.code)
+// prints: 200
+
+println(response.body)
+// prints some JSON string
+```
+{% endtab %}
+{% endtabs %}
+
+A `Response[String]` contains a status code and a string body.
+
+## The request definition
+
+### The HTTP method and URI
+
+To specify the HTTP method and URI of a `quickRequest`, you can use `get`, `post`, `put`, or `delete`.
+
+To construct a URI you can use the `uri` interpolator, for e.g. `uri"https://example.com"`.
+To learn more about that, see [*How to construct URIs and query parameters?*](/toolkit/http-client-uris).
+
+### The headers
+
+By default, the `quickRequest` contains the "Accept-Encoding" and the "deflate" headers.
+To add more headers, you can call one of the `header` or `headers` overloads:
+
+{% tabs 'headers' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc:reset
+import sttp.client4.quick._
+
+val request = quickRequest
+ .get(uri"https://example.com")
+ .header("Origin", "https://scala-lang.org")
+
+println(request.headers)
+// prints: Vector(Accept-Encoding: gzip, deflate, Origin: https://scala-lang.org)
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import sttp.client4.quick.*
+
+val request = quickRequest
+ .get(uri"https://example.com")
+ .header("Origin", "https://scala-lang.org")
+
+println(request.headers)
+// prints: Vector(Accept-Encoding: gzip, deflate, Origin: https://scala-lang.org)
+```
+{% endtab %}
+{% endtabs %}
+
+sttp can also add "Content-Type" and "Content-Length" automatically if the request contains a body.
+
+## Authentication
+
+If you need authentication to access a resource, you can use one of the `auth.basic`, `auth.basicToken`, `auth.bearer` or `auth.digest` methods.
+
+{% tabs 'auth' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc:reset
+import sttp.client4.quick._
+
+// a request with a authentication
+val request = quickRequest
+ .get(uri"https://example.com")
+ .auth.basic(user = "user", password = "***")
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import sttp.client4.quick.*
+
+// a request with a authentication
+val request = quickRequest
+ .get(uri"https://example.com")
+ .auth.basic(user = "user", password = "***")
+```
+{% endtab %}
+{% endtabs %}
diff --git a/_overviews/toolkit/http-client-upload-file.md b/_overviews/toolkit/http-client-upload-file.md
new file mode 100644
index 0000000000..a67af28fbd
--- /dev/null
+++ b/_overviews/toolkit/http-client-upload-file.md
@@ -0,0 +1,80 @@
+---
+title: How to upload a file over HTTP?
+type: section
+description: Uploading a file over HTTP with sttp.
+num: 28
+previous-page: http-client-json
+next-page: http-client-what-else
+---
+
+{% include markdown.html path="_markdown/install-sttp.md" %}
+
+## Uploading a file
+
+To upload a file, you can put a Java `Path` in the body of a request.
+
+You can get a `Path` directly using `Paths.get("path/to/file")` or by converting an OS-Lib path to a Java path with `toNIO`.
+
+{% tabs 'file' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc:compile-only
+import sttp.client4.quick._
+
+val file: java.nio.file.Path = (os.pwd / "image.png").toNIO
+val response = quickRequest.post(uri"https://example.com/").body(file).send()
+
+println(response.code)
+// prints: 200
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import sttp.client4.quick.*
+
+val file: java.nio.file.Path = (os.pwd / "image.png").toNIO
+val response = quickRequest.post(uri"https://example.com/").body(file).send()
+
+println(response.code)
+// prints: 200
+```
+{% endtab %}
+{% endtabs %}
+
+## Multi-part requests
+
+If the web server can receive multiple files at once, you can use a multipart body, as follows:
+
+{% tabs 'multipart' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+import sttp.client4.quick._
+
+val file1 = (os.pwd / "avatar1.png").toNIO
+val file2 = (os.pwd / "avatar2.png").toNIO
+val response = quickRequest
+ .post(uri"https://example.com/")
+ .multipartBody(
+ multipartFile("avatar1.png", file1),
+ multipartFile("avatar2.png", file2)
+ )
+ .send()
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import sttp.client4.quick.*
+
+val file1 = (os.pwd / "avatar1.png").toNIO
+val file2 = (os.pwd / "avatar2.png").toNIO
+val response = quickRequest
+ .post(uri"https://example.com/")
+ .multipartBody(
+ multipartFile("avatar1.png", file1),
+ multipartFile("avatar2.png", file2)
+ )
+ .send()
+```
+{% endtab %}
+{% endtabs %}
+
+Learn more about multipart requests in the [sttp documention](https://sttp.softwaremill.com/en/latest/requests/multipart.html).
diff --git a/_overviews/toolkit/http-client-uris.md b/_overviews/toolkit/http-client-uris.md
new file mode 100644
index 0000000000..bfb9beb332
--- /dev/null
+++ b/_overviews/toolkit/http-client-uris.md
@@ -0,0 +1,128 @@
+---
+title: How to construct URIs and query parameters?
+type: section
+description: Using interpolation to construct URIs
+num: 25
+previous-page: http-client-request
+next-page: http-client-request-body
+---
+
+{% include markdown.html path="_markdown/install-sttp.md" %}
+
+## The `uri` interpolator
+
+`uri` is a custom [string interpolator](/overviews/core/string-interpolation.html) that allows you to create valid web addresses, also called URIs. For example, you can write `uri"https://example.com/"`.
+
+You can insert any variable or expression in your URI with the usual `$` or `${}` syntax.
+For instance `uri"https://example.com/$name"`, interpolates the value of the variable `name` into an URI.
+If `name` contains `"peter"`, the result is `https://example.com/peter`.
+
+`uri` escapes special characters automatically, as seen in this example:
+
+{% tabs 'uri' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc
+import sttp.client4.quick._
+import sttp.model.Uri
+
+val book = "programming in scala"
+val bookUri: Uri = uri"https://example.com/books/$book"
+
+println(bookUri)
+// prints: https://example.com/books/programming%20in%20scala
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import sttp.client4.quick.*
+import sttp.model.Uri
+
+val book = "programming in scala"
+val bookUri: Uri = uri"https://example.com/books/$book"
+
+println(bookUri)
+// prints: https://example.com/books/programming%20in%20scala
+```
+{% endtab %}
+{% endtabs %}
+
+## Query parameters
+
+A query parameter is a key-value pair that is appended to the end of a URI in an HTTP request to specify additional details about the request.
+The web server can use those parameters to compute the appropriate response.
+
+For example, consider the following URL:
+
+```
+https://example.com/search?q=scala&limit=10&page=1
+```
+
+It contains three query parameters: `q=scala`, `limit=10` and `page=1`.
+
+### Using a map of query parameters
+
+The `uri` interpolator can interpolate a `Map[String, String]` as query parameters:
+
+{% tabs 'queryparams' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+val queryParams = Map(
+ "q" -> "scala",
+ "limit" -> "10",
+ "page" -> "1"
+)
+val uriWithQueryParams = uri"https://example.com/search?$queryParams"
+println(uriWithQueryParams)
+// prints: https://example.com/search?q=scala&limit=10&page=1
+```
+{% endtab %}
+{% endtabs %}
+
+For safety, special characters in the parameters are automatically escaped by the interpolator.
+
+## Using an optional query parameter
+
+A query parameter might be optional.
+The `uri` interpolator can interpolate `Option`s:
+
+{% tabs 'optional' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+def getUri(limit: Option[Int]): Uri =
+ uri"https://example.com/all?limit=$limit"
+
+println(getUri(Some(10)))
+// prints: https://example.com/all?limit=100
+
+println(getUri(None))
+// prints: https://example.com/all
+```
+{% endtab %}
+{% endtabs %}
+
+Notice that the query parameter disappears entirely when `limit` is `None`.
+
+## Using a sequence as values of a single query parameter
+
+A query parameter can be repeated in a URI to represent a list of values.
+For example, the `version` parameter in `?version=1.0.0&version=1.0.1&version=1.1.0` contains 3 values: `1.0.0`, `1.0.1` and `1.1.0`.
+
+To build such query parameter in a URI, you can interpolate a `Seq` (or `List`, `Array`, etc) in a `uri"..."`.
+
+{% tabs 'seq' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc:nest
+def getUri(versions: Seq[String]): Uri =
+ uri"https://example.com/scala?version=$versions"
+
+println(getUri(Seq("3.2.2")))
+// prints: https://example.com/scala?version=3.2.2
+
+println(getUri(Seq("2.13.8", "2.13.9", "2.13.10")))
+// prints: https://example.com/scala?version=2.13.8&version=2.13.9&version=2.13.10
+
+println(getUri(Seq.empty))
+// prints: https://example.com/scala
+```
+{% endtab %}
+{% endtabs %}
diff --git a/_overviews/toolkit/http-client-what-else.md b/_overviews/toolkit/http-client-what-else.md
new file mode 100644
index 0000000000..11b577449d
--- /dev/null
+++ b/_overviews/toolkit/http-client-what-else.md
@@ -0,0 +1,112 @@
+---
+title: What else can sttp do?
+type: section
+description: An incomplete list of features of sttp
+num: 29
+previous-page: http-client-upload-file
+next-page:
+---
+
+{% include markdown.html path="_markdown/install-upickle.md" %}
+
+## Asynchronous requests
+
+To send a request asynchronously you can use a `DefaultFutureBackend`:
+
+{% tabs 'async' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc
+import scala.concurrent.Future
+import sttp.client4._
+
+val asyncBackend = DefaultFutureBackend()
+val response: Future[Response[String]] = quickRequest
+ .get(uri"https://example.com")
+ .send(asyncBackend)
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import scala.concurrent.Future
+import sttp.client4.*
+
+val asyncBackend = DefaultFutureBackend()
+val response: Future[Response[String]] = quickRequest
+ .get(uri"https://example.com")
+ .send(asyncBackend)
+```
+{% endtab %}
+{% endtabs %}
+
+You can learn more about `Future`-based backends in the [sttp documentation](https://sttp.softwaremill.com/en/latest/backends/future.html).
+
+sttp supports other asynchronous wrappers such as Monix `Task`s, cats-effect `Effect`s, ZIO's `ZIO` type, and more.
+You can see the full list of supported backends [here](https://sttp.softwaremill.com/en/latest/backends/summary.html).
+
+## Websockets
+
+You can use a `DefaultFutureBackend` to open a websocket, as follows.
+
+{% tabs 'ws' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc:reset
+import scala.concurrent.duration.Duration
+import scala.concurrent.{Await, Future}
+import scala.concurrent.ExecutionContext.Implicits.global
+
+import sttp.client4._
+import sttp.ws.WebSocket
+
+val asyncBackend = DefaultFutureBackend()
+
+def useWebSocket(ws: WebSocket[Future]): Future[Unit] =
+ for {
+ _ <- ws.sendText("Hello")
+ text <- ws.receiveText()
+ } yield {
+ println(text)
+ }
+
+val response = quickRequest
+ .get(uri"wss://ws.postman-echo.com/raw")
+ .response(asWebSocketAlways(useWebSocket))
+ .send(asyncBackend)
+
+Await.result(response, Duration.Inf)
+// prints: Hello
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import scala.concurrent.duration.Duration
+import scala.concurrent.{Await, Future}
+import scala.concurrent.ExecutionContext.Implicits.global
+
+import sttp.client4.*
+import sttp.ws.WebSocket
+
+val asyncBackend = DefaultFutureBackend()
+
+def useWebSocket(ws: WebSocket[Future]): Future[Unit] =
+ for
+ _ <- ws.sendText("Hello")
+ text <- ws.receiveText()
+ yield
+ println(text)
+
+val response = quickRequest
+ .get(uri"wss://ws.postman-echo.com/raw")
+ .response(asWebSocketAlways(useWebSocket))
+ .send(asyncBackend)
+
+Await.result(response, Duration.Inf)
+// prints: Hello
+```
+{% endtab %}
+{% endtabs %}
+
+Learn more about WebSockets in [sttp documentation](https://sttp.softwaremill.com/en/latest/websockets.html).
+
+## More features
+
+You can discover many more features such as streaming, logging, timeouts, and more in [sttp documentation](https://sttp.softwaremill.com/en/latest/quickstart.html#).
diff --git a/_overviews/toolkit/introduction.md b/_overviews/toolkit/introduction.md
new file mode 100644
index 0000000000..1656ed9662
--- /dev/null
+++ b/_overviews/toolkit/introduction.md
@@ -0,0 +1,77 @@
+---
+title: Introduction
+type: chapter
+description: Introducing the Scala Toolkit tutorials
+num: 1
+previous-page:
+next-page: testing-intro
+toolkit-index:
+ - title: Tests
+ description: Testing code with MUnit.
+ icon: "fa fa-vial-circle-check"
+ link: /toolkit/testing-intro.html
+ - title: Files and Processes
+ description: Writing files and running processes with OS-Lib.
+ icon: "fa fa-folder-open"
+ link: /toolkit/os-intro.html
+ - title: JSON
+ description: Parsing JSON and serializing objects to JSON with uPickle.
+ icon: "fa fa-file-code"
+ link: /toolkit/json-intro.html
+ - title: HTTP Requests
+ description: Sending HTTP requests and uploading files with sttp.
+ icon: "fa fa-globe"
+ link: /toolkit/http-client-intro.html
+---
+
+## What is the Scala Toolkit?
+
+The Scala Toolkit is a set of libraries designed to effectively perform common programming tasks. It includes tools for working with files and processes, parsing JSON, sending HTTP requests, and unit testing.
+
+The Toolkit supports:
+* Scala 3 and Scala 2
+* JVM, Scala.js, and Scala Native
+
+Use cases for the Toolkit include:
+
+- short-lived programs running on the JVM, to scrape a website, to collect and transform data, or to fetch and process some files,
+- frontend scripts that run on the browser and power your websites,
+- command-line tools packaged as native binaries for instant startup
+
+{% include inner-documentation-sections.html links=page.toolkit-index %}
+
+## What are these tutorials?
+
+This series of tutorials focuses on short code examples, to help you get started quickly.
+
+If you need more in-depth information, the tutorials include links to further documentation for all of the libraries in the toolkit.
+
+## How to run the code?
+
+You can follow the tutorials regardless of how you choose to run your
+Scala code. The tutorials focus on the code itself, not on the process
+of running it.
+
+Ways to run Scala code include:
+* in your **browser** with [Scastie](https://scastie.scala-lang.org)
+ * pros: zero installation, online sharing
+ * cons: single-file only, online-only
+* interactively in the Scala **REPL** (Read/Eval/Print Loop)
+ * pros: interactive exploration in the terminal
+ * cons: doesn't save your code anywhere
+* interactively in a **worksheet** in your IDE such as [IntelliJ](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html) or [Metals](http://scalameta.org/metals/)
+ * pros: interactive exploration in a GUI
+ * cons: requires worksheet environment to run
+* in **scripts**, using [Scala CLI](https://scala-cli.virtuslab.com)
+ * pros: terminal-based workflow with little setup
+ * cons: may not be suitable for large projects
+* using a **build tool** (such as [sbt](https://www.scala-sbt.org) or [mill](https://com-lihaoyi.github.io/mill/))
+ * pros: terminal-based workflow for projects of any size
+ * cons: requires some additional setup and learning
+* using an **IDE** such as [IntelliJ](https://www.jetbrains.com/help/idea/discover-intellij-idea-for-scala.html) or [Metals](http://scalameta.org/metals/)
+ * pros: GUI based workflow for projects of any size
+ * cons: requires some additional setup and learning
+
+These choices, with their pros and cons, are common to most programing
+languages.
+Feel free to use whichever option you're most comfortable with.
diff --git a/_overviews/toolkit/json-deserialize.md b/_overviews/toolkit/json-deserialize.md
new file mode 100644
index 0000000000..23fd6391d1
--- /dev/null
+++ b/_overviews/toolkit/json-deserialize.md
@@ -0,0 +1,121 @@
+---
+title: How to deserialize JSON to an object?
+type: section
+description: Parsing JSON to a custom data type
+num: 19
+previous-page: json-modify
+next-page: json-serialize
+---
+
+{% include markdown.html path="_markdown/install-upickle.md" %}
+
+## Parsing vs. deserialization
+
+Parsing with uJson only accepts valid JSON, but it does not validate that the names and types of fields are as expected.
+
+Deserialization, on the other hand, transforms a JSON string to some user-specified Scala data type, if required fields are present and have the correct types.
+
+In this tutorial, we show how to deserialize to a `Map` and also to a custom `case class`.
+
+## Deserializing JSON to a `Map`
+
+For a type `T`, uPickle can deserialize JSON to a `Map[String, T]`, checking that all fields conform to `T`.
+
+We can for instance, deserialize to a `Map[String, List[Int]]`:
+
+{% tabs 'parsemap' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+val json = """{"primes": [2, 3, 5], "evens": [2, 4, 6]} """
+val map: Map[String, List[Int]] =
+ upickle.default.read[Map[String, List[Int]]](json)
+
+println(map("primes"))
+// prints: List(2, 3, 5)
+```
+{% endtab %}
+{% endtabs %}
+
+If a value is the wrong type, uPickle throws a `upickle.core.AbortException`.
+
+{% tabs 'parsemap-error' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc:reset:crash
+val json = """{"name": "Peter"} """
+upickle.default.read[Map[String, List[Int]]](json)
+// throws: upickle.core.AbortException: expected sequence got string at index 9
+```
+{% endtab %}
+{% endtabs %}
+
+### Deserializing JSON to a custom data type
+
+In Scala, you can use a `case class` to define your own data type.
+For example, to represent a pet owner, you might:
+```scala mdoc:reset
+case class PetOwner(name: String, pets: List[String])
+```
+
+To read a `PetOwner` from JSON, we must provide a `ReadWriter[PetOwner]`.
+uPickle can do that automatically:
+
+{% tabs 'given' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc
+import upickle.default._
+
+implicit val ownerRw: ReadWriter[PetOwner] = macroRW[PetOwner]
+```
+Some explanations:
+- An `implicit val` is a value that can be automatically provided as an argument to a method or function call, without having to explicitly pass it.
+- `macroRW` is a method provided by uPickle that can generate a instances of `ReadWriter` for case classes, using the information about its fields.
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import upickle.default.*
+
+case class PetOwner(name: String, pets: List[String])
+ derives ReadWriter
+```
+The `derives` keyword is used to automatically generate given instances.
+Using the compiler's knowledge of the fields in `PetOwner`, it generates a `ReadWriter[PetOwner]`.
+{% endtab %}
+{% endtabs %}
+
+This means that you can now read (and write) `PetOwner` objects from JSON with `upickle.default.read(petOwner)`.
+
+Notice that you do not need to pass the instance of `ReadWriter[PetOwner]` explicitly to the `read` method. But it does, nevertheless, get it from the context, as "given" value. You may find more information about contextual abstractions in the [Scala 3 Book](https://docs.scala-lang.org/scala3/book/ca-contextual-abstractions-intro.html).
+
+Putting everything together you should get:
+
+{% tabs 'full' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc:reset
+import upickle.default._
+
+case class PetOwner(name: String, pets: List[String])
+implicit val ownerRw: ReadWriter[PetOwner] = macroRW
+
+val json = """{"name": "Peter", "pets": ["Toolkitty", "Scaniel"]}"""
+val petOwner: PetOwner = read[PetOwner](json)
+
+val firstPet = petOwner.pets.head
+println(s"${petOwner.name} has a pet called $firstPet")
+// prints: Peter has a pet called Toolkitty
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import upickle.default.*
+
+case class PetOwner(name: String, pets: List[String]) derives ReadWriter
+
+val json = """{"name": "Peter", "pets": ["Toolkitty", "Scaniel"]}"""
+val petOwner: PetOwner = read[PetOwner](json)
+
+val firstPet = petOwner.pets.head
+println(s"${petOwner.name} has a pet called $firstPet")
+// prints: Peter has a pet called Toolkitty
+```
+{% endtab %}
+{% endtabs %}
diff --git a/_overviews/toolkit/json-files.md b/_overviews/toolkit/json-files.md
new file mode 100644
index 0000000000..63fd2759ec
--- /dev/null
+++ b/_overviews/toolkit/json-files.md
@@ -0,0 +1,72 @@
+---
+title: How to read and write JSON files?
+type: section
+description: Reading and writing JSON files using UPickle and OSLib
+num: 21
+previous-page: json-serialize
+next-page: json-what-else
+---
+
+{% include markdown.html path="_markdown/install-upickle.md" %}
+
+## Read and write raw JSON
+
+To read and write JSON files, you can use uJson and OS-Lib as follows:
+
+{% tabs 'raw' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc:compile-only
+// read a JSON file
+val json = ujson.read(os.read(os.pwd / "raw.json"))
+
+// modify the JSON content
+json("updated") = "now"
+
+//write to a new file
+os.write(os.pwd / "raw-updated.json", ujson.write(json))
+```
+{% endtab %}
+{% endtabs %}
+
+## Read and write Scala objects using JSON
+
+To read and write Scala objects to and from JSON files, you can use uPickle and OS-Lib as follows:
+
+{% tabs 'object' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc:compile-only
+import upickle.default._
+
+case class PetOwner(name: String, pets: List[String])
+implicit val ownerRw: ReadWriter[PetOwner] = macroRW
+
+// read a PetOwner from a JSON file
+val petOwner: PetOwner = read[PetOwner](os.read(os.pwd / "pet-owner.json"))
+
+// create a new PetOwner
+val petOwnerUpdated = petOwner.copy(pets = "Toolkitty" :: petOwner.pets)
+
+// write to a new file
+os.write(os.pwd / "pet-owner-updated.json", write(petOwnerUpdated))
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import upickle.default.*
+
+case class PetOwner(name: String, pets: List[String]) derives ReadWriter
+
+// read a PetOwner from a JSON file
+val petOwner: PetOwner = read[PetOwner](os.read(os.pwd / "pet-owner.json"))
+
+// create a new PetOwner
+val petOwnerUpdated = petOwner.copy(pets = "Toolkitty" :: petOwner.pets)
+
+// write to a new file
+os.write(os.pwd / "pet-owner-updated.json", write(petOwnerUpdated))
+```
+{% endtab %}
+{% endtabs %}
+
+To serialize and deserialize Scala case classes (or enums) to JSON we need an instance of `ReadWriter`.
+To understand how uPickle generates it for you, you can read the [*How to deserialize JSON to an object?*](/toolkit/json-deserialize) or the [*How to serialize an object to JSON?*](/toolkit/json-serialize) tutorials.
diff --git a/_overviews/toolkit/json-intro.md b/_overviews/toolkit/json-intro.md
new file mode 100644
index 0000000000..7fb3e890de
--- /dev/null
+++ b/_overviews/toolkit/json-intro.md
@@ -0,0 +1,16 @@
+---
+title: Handling JSON with uPickle
+type: chapter
+description: Description of the uPickle library.
+num: 16
+previous-page: os-what-else
+next-page: json-parse
+---
+
+uPickle is a lightweight serialization library for Scala.
+
+It includes uJson, a JSON manipulation library that can parse JSON strings, access or mutate their values in memory, and write them back out again.
+
+uPickle can serialize and deserialize Scala objects directly to and from JSON. It knows how to handle the Scala collections such as `Map` and `Seq`, as well as your own data types, such as `case class`s and Scala 3 `enum`s.
+
+{% include markdown.html path="_markdown/install-upickle.md" %}
diff --git a/_overviews/toolkit/json-modify.md b/_overviews/toolkit/json-modify.md
new file mode 100644
index 0000000000..8f1cd63e6d
--- /dev/null
+++ b/_overviews/toolkit/json-modify.md
@@ -0,0 +1,33 @@
+---
+title: How to modify JSON?
+type: section
+description: Modifying JSON with uPickle.
+num: 18
+previous-page: json-parse
+next-page: json-deserialize
+---
+
+{% include markdown.html path="_markdown/install-upickle.md" %}
+
+`ujson.read` returns a mutable representation of JSON that you can update. Fields and elemnts can be added, modified, or removed.
+
+First you read the JSON string, then you update it in memory, and finally you write it back out again.
+
+{% tabs 'modify' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+// Parse the JSON string
+val json: ujson.Value = ujson.read("""{"name":"John","pets":["Toolkitty","Scaniel"]}""")
+
+// Update it
+json("name") = "Peter"
+json("nickname") = "Pete"
+json("pets").arr.remove(1)
+
+// Write it back to a String
+val result: String = ujson.write(json)
+println(result)
+// prints: {"name":"Peter","pets":["Toolkitty"],"nickname":"Pete"}
+```
+{% endtab %}
+{% endtabs %}
diff --git a/_overviews/toolkit/json-parse.md b/_overviews/toolkit/json-parse.md
new file mode 100644
index 0000000000..71b1fab391
--- /dev/null
+++ b/_overviews/toolkit/json-parse.md
@@ -0,0 +1,82 @@
+---
+title: How to access values inside JSON?
+type: section
+description: Accessing JSON values using ujson.
+num: 17
+previous-page: json-intro
+next-page: json-modify
+---
+
+{% include markdown.html path="_markdown/install-upickle.md" %}
+
+## Accessing values inside JSON
+
+To parse a JSON string and access fields inside it, you can use uJson, which is part of uPickle.
+
+The method `ujson.read` parses a JSON string into memory:
+{% tabs 'read' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+val jsonString = """{"name": "Peter", "age": 13, "pets": ["Toolkitty", "Scaniel"]}"""
+val json: ujson.Value = ujson.read(jsonString)
+println(json("name").str)
+// prints: Peter
+```
+{% endtab %}
+{% endtabs %}
+
+To access the `"name"` field, we do `json("name")` and then call `str` to type it as a string.
+
+To access the elements by index in a JSON array,
+
+{% tabs 'array' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+val pets: ujson.Value = json("pets")
+
+val firstPet: String = pets(0).str
+val secondPet: String = pets(1).str
+
+println(s"The pets are $firstPet and $secondPet")
+// prints: The pets are Toolkitty and Scaniel
+```
+{% endtab %}
+{% endtabs %}
+
+You can traverse the JSON structure as deeply as you want, to extract any nested value.
+For instance, `json("field1")(0)("field2").str` is the string value of `field2` in the first element of the array in `field1`.
+
+## JSON types
+
+In the previous examples we used `str` to type a JSON value as a string.
+Similar methods exist for other types of values, namely:
+ - `num` for numeric values, returning `Double`
+ - `bool` for boolean values, returning `Boolean`
+ - `arr` for arrays, returning a mutable `Buffer[ujson.Value]`
+ - `obj` for objects, returning a mutable `Map[String, ujson.Value]`
+
+{% tabs 'typing' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc:reset
+import scala.collection.mutable
+
+val jsonString = """{"name": "Peter", "age": 13, "pets": ["Toolkitty", "Scaniel"]}"""
+val json = ujson.read(jsonString)
+
+val person: mutable.Map[String, ujson.Value] = json.obj
+val age: Double = person("age").num
+val pets: mutable.Buffer[ujson.Value] = person("pets").arr
+```
+{% endtab %}
+{% endtabs %}
+
+If a JSON value does not conform to the expected type, uJson throws a `ujson.Value.InvalidData` exception.
+
+{% tabs 'exception' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc:crash
+val name: Boolean = person("name").bool
+// throws a ujson.Value.InvalidData: Expected ujson.Bool (data: "Peter")
+```
+{% endtab %}
+{% endtabs %}
diff --git a/_overviews/toolkit/json-serialize.md b/_overviews/toolkit/json-serialize.md
new file mode 100644
index 0000000000..bd3049def0
--- /dev/null
+++ b/_overviews/toolkit/json-serialize.md
@@ -0,0 +1,95 @@
+---
+title: How to serialize an object to JSON?
+type: section
+description: How to write JSON with Scala Toolkit.
+num: 20
+previous-page: json-deserialize
+next-page: json-files
+---
+
+{% include markdown.html path="_markdown/install-upickle.md" %}
+
+## Serializing a Map to JSON
+
+uPickle can serialize your Scala objects to JSON, so that you can save them in files or send them over the network.
+
+By default it can serialize primitive types such as `Int` or `String`, as well as standard collections such as `Map` and `List`.
+
+{% tabs 'array' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+val map: Map[String, Int] =
+ Map("Toolkitty" -> 3, "Scaniel" -> 5)
+val jsonString: String = upickle.default.write(map)
+println(jsonString)
+// prints: {"Toolkitty":3,"Scaniel":5}
+```
+{% endtab %}
+{% endtabs %}
+
+## Serializing a custom object to JSON
+
+In Scala, you can use a `case class` to define your own data type.
+For example, to represent a pet owner with the name of its pets, you can
+```scala mdoc:reset
+case class PetOwner(name: String, pets: List[String])
+```
+
+To be able to write a `PetOwner` to JSON we need to provide a `ReadWriter` instance for the case class `PetOwner`.
+Luckily, `upickle` is able to fully automate that:
+
+{% tabs 'given' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc
+import upickle.default._
+
+implicit val ownerRw: ReadWriter[PetOwner] = macroRW[PetOwner]
+```
+Some explanations:
+- An `implicit val` is a value that can be automatically provided as an argument to a method or function call, without having to explicitly pass it.
+- `macroRW` is a method provided by uPickle that can generate a instances of `ReadWriter` for case classes, using the information about its fields.
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import upickle.default.*
+
+case class PetOwner(name: String, pets: List[String]) derives ReadWriter
+```
+The `derives` keyword is used to automatically generate given instances.
+Using the compiler's knowledge of the fields in `PetOwner`, it generates a `ReadWriter[PetOwner]`.
+{% endtab %}
+{% endtabs %}
+
+This means that you can now write (and read) `PetOwner` objects to JSON with `upickle.default.write(petOwner)`.
+
+Notice that you do not need to pass the instance of `ReadWriter[PetOwner]` explicitly to the `write` method. But it does, nevertheless, get it from the context, as "given" value. You may find more information about contextual abstractions in the [Scala 3 Book](https://docs.scala-lang.org/scala3/book/ca-contextual-abstractions-intro.html).
+
+Putting everything together you should get:
+
+{% tabs 'full' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+import upickle.default._
+
+case class PetOwner(name: String, pets: List[String])
+implicit val ownerRw: ReadWriter[PetOwner] = macroRW
+
+val petOwner = PetOwner("Peter", List("Toolkitty", "Scaniel"))
+val json: String = write(petOwner)
+println(json)
+// prints: {"name":"Peter","pets":["Toolkitty","Scaniel"]}"
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import upickle.default._
+
+case class PetOwner(name: String, pets: List[String]) derives ReadWriter
+
+val petOwner = PetOwner("Peter", List("Toolkitty", "Scaniel"))
+val json: String = write(petOwner)
+println(json)
+// prints: {"name":"Peter","pets":["Toolkitty","Scaniel"]}"
+```
+{% endtab %}
+{% endtabs %}
diff --git a/_overviews/toolkit/json-what-else.md b/_overviews/toolkit/json-what-else.md
new file mode 100644
index 0000000000..1f34daffe4
--- /dev/null
+++ b/_overviews/toolkit/json-what-else.md
@@ -0,0 +1,74 @@
+---
+title: What else can uPickle do?
+type: section
+description: An incomplete list of features of uPickle
+num: 22
+previous-page: json-files
+next-page: http-client-intro
+---
+
+{% include markdown.html path="_markdown/install-upickle.md" %}
+## Construct a new JSON structure with uJson
+
+{% tabs construct%}
+{% tab 'Scala 2 and Scala 3' %}
+```scala mdoc
+val obj: ujson.Value = ujson.Obj(
+ "library" -> "upickle",
+ "versions" -> ujson.Arr("1.6.0", "2.0.0", "3.1.0"),
+ "documentation" -> "https://com-lihaoyi.github.io/upickle/",
+)
+```
+{% endtab %}
+{% endtabs %}
+
+Learn more about constructing JSON in the [uJson documentation](https://com-lihaoyi.github.io/upickle/#Construction).
+
+## Defining custom JSON serialization
+
+You can customize the `ReadWriter` of your data type by mapping the `ujson.Value`, like this:
+
+{% tabs custom-serializer class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc
+import upickle.default._
+
+case class Bar(i: Int, s: String)
+
+object Bar {
+ implicit val barReadWriter: ReadWriter[Bar] = readwriter[ujson.Value]
+ .bimap[Bar](
+ x => ujson.Arr(x.s, x.i),
+ json => new Bar(json(1).num.toInt, json(0).str)
+ )
+}
+
+val bar = Bar(5, "bar")
+val json = upickle.default.write(bar)
+println(json)
+// prints: [5, "bar"]
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import upickle.default.*
+
+case class Bar(i: Int, s: String)
+
+object Bar:
+ given ReadWriter[Bar] = readwriter[ujson.Value]
+ .bimap[Bar](
+ x => ujson.Arr(x.s, x.i),
+ json => new Bar(json(1).num.toInt, json(0).str)
+ )
+
+val bar = Bar(5, "bar")
+val json = upickle.default.write(bar)
+println(json)
+// prints: [5, "bar"]
+```
+{% endtab %}
+{% endtabs %}
+
+Learn more about custom JSON serialization in the [uPickle documentation](https://com-lihaoyi.github.io/upickle/#Customization).
+
diff --git a/_overviews/toolkit/os-intro.md b/_overviews/toolkit/os-intro.md
new file mode 100644
index 0000000000..71d30f0c49
--- /dev/null
+++ b/_overviews/toolkit/os-intro.md
@@ -0,0 +1,20 @@
+---
+title: Working with files and processes with OS-Lib
+type: chapter
+description: The introduction of the OS-lib library
+num: 10
+previous-page: testing-what-else
+next-page: os-read-directory
+---
+
+OS-Lib is a library for manipulating files and processes. It is part of the Scala Toolkit.
+
+OS-Lib aims to replace the `java.nio.file` and `java.lang.ProcessBuilder` APIs. You should not need to use any underlying Java APIs directly.
+
+OS-lib also aims to supplant the older `scala.io` and `scala.sys` APIs in the Scala standard library.
+
+OS-Lib has no dependencies.
+
+All of OS-Lib is in the `os.*` namespace.
+
+{% include markdown.html path="_markdown/install-os-lib.md" %}
diff --git a/_overviews/toolkit/os-read-directory.md b/_overviews/toolkit/os-read-directory.md
new file mode 100644
index 0000000000..b24b664a49
--- /dev/null
+++ b/_overviews/toolkit/os-read-directory.md
@@ -0,0 +1,59 @@
+---
+title: How to read a directory?
+type: section
+description: Reading a directory's contents with OS-Lib
+num: 11
+previous-page: os-intro
+next-page: os-read-file
+---
+
+{% include markdown.html path="_markdown/install-os-lib.md" %}
+
+## Paths
+
+A fundamental data type in OS-Lib is `os.Path`, representing a path
+on the filesystem. An `os.Path` is always an absolute path.
+
+OS-Lib also provides `os.RelPath` (relative paths) and `os.SubPath` (a
+relative path which cannot ascend to parent directories).
+
+Typical starting points for making paths are `os.pwd` (the
+current working directory), `os.home` (the current user's home
+directory), `os.root` (the root of the filesystem), or
+`os.temp.dir()` (a new temporary directory).
+
+Paths have a `/` method for adding path segments. For example:
+
+{% tabs 'etc' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+val etc: os.Path = os.root / "etc"
+```
+{% endtab %}
+{% endtabs %}
+
+## Reading a directory
+
+`os.list` returns the contents of a directory:
+
+{% tabs 'list-etc' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+val entries: Seq[os.Path] = os.list(os.root / "etc")
+```
+{% endtab %}
+{% endtabs %}
+
+Or if we only want subdirectories:
+
+{% tabs 'subdirs' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+val dirs: Seq[os.Path] = os.list(os.root / "etc").filter(os.isDir)
+```
+{% endtab %}
+{% endtabs %}
+
+To recursively descend an entire subtree, change `os.list` to
+`os.walk`. To process results on the fly rather than reading them all
+into memory first, substitute `os.walk.stream`.
diff --git a/_overviews/toolkit/os-read-file.md b/_overviews/toolkit/os-read-file.md
new file mode 100644
index 0000000000..bc23ca2588
--- /dev/null
+++ b/_overviews/toolkit/os-read-file.md
@@ -0,0 +1,64 @@
+---
+title: How to read a file?
+type: section
+description: Reading files from disk with OS-Lib
+num: 12
+previous-page: os-read-directory
+next-page: os-write-file
+---
+
+{% include markdown.html path="_markdown/install-os-lib.md" %}
+
+## Reading a file
+
+Supposing we have the path to a file:
+
+{% tabs 'path' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+val path: os.Path = os.root / "usr" / "share" / "dict" / "words"
+```
+{% endtab %}
+{% endtabs %}
+
+Then we can slurp the entire file into a string with `os.read`:
+
+{% tabs slurp %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc:compile-only
+val content: String = os.read(path)
+```
+{% endtab %}
+{% endtabs %}
+
+To read the file as line at a time, substitute `os.read.lines`.
+
+We can find the longest word in the dictionary:
+
+{% tabs lines %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc:compile-only
+val lines: Seq[String] = os.read.lines(path)
+println(lines.maxBy(_.size))
+// prints: antidisestablishmentarianism
+```
+{% endtab %}
+{% endtabs %}
+
+There's also `os.read.lines.stream` if you want to process the lines
+on the fly rather than read them all into memory at once. For example,
+if we just want to read the first line, the most efficient way is:
+
+{% tabs lines-stream %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc:compile-only
+val lineStream: geny.Generator[String] = os.read.lines.stream(path)
+val firstLine: String = lineStream.head
+println(firstLine)
+// prints: A
+```
+{% endtab %}
+{% endtabs %}
+
+OS-Lib takes care of closing the file once the generator returned
+by `stream` is exhausted.
diff --git a/_overviews/toolkit/os-run-process.md b/_overviews/toolkit/os-run-process.md
new file mode 100644
index 0000000000..7bbe4ace58
--- /dev/null
+++ b/_overviews/toolkit/os-run-process.md
@@ -0,0 +1,79 @@
+---
+title: How to run a process?
+type: section
+description: Starting external subprocesses with OS-Lib
+num: 14
+previous-page: os-write-file
+next-page: os-what-else
+---
+
+{% include markdown.html path="_markdown/install-os-lib.md" %}
+
+## Starting an external process
+
+To set up a process, use `os.proc`, then to actually start it,
+`call()`:
+
+{% tabs 'touch' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc:compile-only
+val path: os.Path = os.pwd / "output.txt"
+println(os.exists(path))
+// prints: false
+val result: os.CommandResult = os.proc("touch", path).call()
+println(result.exitCode)
+// prints: 0
+println(os.exists(path))
+// prints: true
+```
+{% endtab %}
+{% endtabs %}
+
+Note that `proc` accepts both strings and `os.Path`s.
+
+## Reading the output of a process
+
+(The particular commands in the following examples might not exist on all
+machines.)
+
+Above we saw that `call()` returned an `os.CommandResult`. We can
+access the result's entire output with `out.text()`, or as lines
+with `out.lines()`.
+
+For example, we could use `bc` to do some math for us:
+
+{% tabs 'bc' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc:compile-only
+val res: os.CommandResult = os.proc("bc", "-e", "2 + 2").call()
+val text: String = res.out.text()
+println(text.trim.toInt)
+// prints: 4
+```
+{% endtab %}
+{% endtabs %}
+
+Or have `cal` show us a calendar:
+
+{% tabs 'cal' %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc:compile-only
+val res: os.CommandResult = os.proc("cal", "-h", "2", "2023").call()
+res.out.lines().foreach(println)
+// prints:
+// February 2023
+// Su Mo Tu We Th Fr Sa
+// 1 2 3 4
+// ...
+```
+{% endtab %}
+{% endtabs %}
+
+## Customizing the process
+
+`call()` takes various optional arguments, too many to explain
+individually here. For example, you can set the working directory
+(`cwd = ...`), set environment variables (`env = ...`), or redirect
+input and output (`stdin = ...`, `stdout = ...`, `stderr = ...`).
+Find more information about the `call` method on [the README of OS-Lib](https://github.com/com-lihaoyi/os-lib#osproccall).
+
diff --git a/_overviews/toolkit/os-what-else.md b/_overviews/toolkit/os-what-else.md
new file mode 100644
index 0000000000..1671540845
--- /dev/null
+++ b/_overviews/toolkit/os-what-else.md
@@ -0,0 +1,18 @@
+---
+title: What else can OS-Lib do?
+type: section
+description: An incomplete list of features of OS-Lib
+num: 15
+previous-page: os-run-process
+next-page: json-intro
+---
+
+{% include markdown.html path="_markdown/install-os-lib.md" %}
+
+[OS-Lib on GitHub](https://github.com/com-lihaoyi/os-lib) has many additional examples of how to perform common tasks:
+- creating, moving, copying, removing files and folders,
+- reading filesystem metadata and permissions,
+- spawning subprocesses,
+- watching changes in folders.
+
+See also Chapter 7 of Li Haoyi's book [_Hands-On Scala Programming_](https://www.handsonscala.com). (Li Haoyi is the author of OS-Lib.)
diff --git a/_overviews/toolkit/os-write-file.md b/_overviews/toolkit/os-write-file.md
new file mode 100644
index 0000000000..2bef6fff0c
--- /dev/null
+++ b/_overviews/toolkit/os-write-file.md
@@ -0,0 +1,54 @@
+---
+title: How to write a file?
+type: section
+description: Writing files to disk with OS-Lib
+num: 13
+previous-page: os-read-file
+next-page: os-run-process
+---
+
+{% include markdown.html path="_markdown/install-os-lib.md" %}
+
+## Writing a file all at once
+
+`os.write` writes the supplied string to a new file:
+
+{% tabs write %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+val path: os.Path = os.temp.dir() / "output.txt"
+os.write(path, "hello\nthere\n")
+println(os.read.lines(path).size)
+// prints: 2
+```
+{% endtab %}
+{% endtabs %}
+
+## Overwriting or appending
+
+`os.write` throws an exception if the file already exists:
+
+{% tabs already-exists %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc:crash
+os.write(path, "this will fail")
+// this exception is thrown:
+// java.nio.file.FileAlreadyExistsException
+```
+{% endtab %}
+{% endtabs %}
+
+To avoid this, use `os.write.over` to replace the existing
+contents.
+
+You can also use `os.write.append` to add more to the end:
+
+{% tabs append %}
+{% tab 'Scala 2 and 3' %}
+```scala mdoc
+os.write.append(path, "two more\nlines\n")
+println(os.read.lines(path).size)
+// prints: 4
+```
+{% endtab %}
+{% endtabs %}
diff --git a/_overviews/toolkit/testing-asynchronous.md b/_overviews/toolkit/testing-asynchronous.md
new file mode 100644
index 0000000000..68862cc1cc
--- /dev/null
+++ b/_overviews/toolkit/testing-asynchronous.md
@@ -0,0 +1,87 @@
+---
+title: How to write asynchronous tests?
+type: section
+description: Writing asynchronous tests using MUnit
+num: 7
+previous-page: testing-exceptions
+next-page: testing-resources
+---
+
+{% include markdown.html path="_markdown/install-munit.md" %}
+
+## Asynchronous tests
+
+In Scala, it's common for an *asynchronous* method to return a `Future`.
+MUnit offers special support for `Future`s.
+
+For example, consider an asynchronous variant of a `square` method:
+
+{% tabs 'async-1' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc
+import scala.concurrent.{ExecutionContext, Future}
+
+object AsyncMathLib {
+ def square(x: Int)(implicit ec: ExecutionContext): Future[Int] =
+ Future(x * x)
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import scala.concurrent.{ExecutionContext, Future}
+
+object AsyncMathLib:
+ def square(x: Int)(using ExecutionContext): Future[Int] =
+ Future(x * x)
+```
+{% endtab %}
+{% endtabs %}
+
+A test itself can return a `Future[Unit]`.
+MUnit will wait behind the scenes for the resulting `Future` to complete, failing the test if any assertion fails.
+
+You can therefore write the test as follows:
+
+{% tabs 'async-3' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc
+// Import the global execution context, required to call async methods
+import scala.concurrent.ExecutionContext.Implicits.global
+
+class AsyncMathLibTests extends munit.FunSuite {
+ test("square") {
+ for {
+ squareOf3 <- AsyncMathLib.square(3)
+ squareOfMinus4 <- AsyncMathLib.square(-4)
+ } yield {
+ assertEquals(squareOf3, 9)
+ assertEquals(squareOfMinus4, 16)
+ }
+ }
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+// Import the global execution context, required to call async methods
+import scala.concurrent.ExecutionContext.Implicits.global
+
+class AsyncMathLibTests extends munit.FunSuite:
+ test("square"):
+ for
+ squareOf3 <- AsyncMathLib.square(3)
+ squareOfMinus4 <- AsyncMathLib.square(-4)
+ yield
+ assertEquals(squareOf3, 9)
+ assertEquals(squareOfMinus4, 16)
+```
+{% endtab %}
+{% endtabs %}
+
+The test first asynchronously computes `square(3)` and `square(-4)`.
+Once both computations are completed, and if they are both successful, it proceeds with the calls to `assertEquals`.
+If any of the assertion fails, the resulting `Future[Unit]` fails, and MUnit will cause the test to fail.
+
+You may read more about asynchronous tests [in the MUnit documentation](https://scalameta.org/munit/docs/tests.html#declare-async-test).
+It shows how to use other asynchronous types besides `Future`.
diff --git a/_overviews/toolkit/testing-exceptions.md b/_overviews/toolkit/testing-exceptions.md
new file mode 100644
index 0000000000..0e26e51bd5
--- /dev/null
+++ b/_overviews/toolkit/testing-exceptions.md
@@ -0,0 +1,65 @@
+---
+title: How to test exceptions?
+type: section
+description: Describe the intercept assertion
+num: 6
+previous-page: testing-run-only
+next-page: testing-asynchronous
+---
+
+{% include markdown.html path="_markdown/install-munit.md" %}
+
+## Intercepting an exception
+
+In a test, you can use `intercept` to check that your code throws an exception.
+
+{% tabs 'intercept-1' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc
+import java.nio.file.NoSuchFileException
+
+class FileTests extends munit.FunSuite {
+ test("read missing file") {
+ val missingFile = os.pwd / "missing.txt"
+
+ intercept[NoSuchFileException] {
+ os.read(missingFile)
+ }
+ }
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import java.nio.file.NoSuchFileException
+
+class FileTests extends munit.FunSuite:
+ test("read missing file"):
+ val missingFile = os.pwd / "missing.txt"
+ intercept[NoSuchFileException]:
+ // the code that should throw an exception
+ os.read(missingFile)
+```
+{% endtab %}
+{% endtabs %}
+
+The type parameter of the `intercept` assertion is the expected exception.
+Here it is `NoSuchFileException`.
+The body of the `intercept` assertion contains the code that should throw the exception.
+
+The test passes if the code throws the expected exception and it fails otherwise.
+
+The `intercept` method returns the exception that is thrown.
+You can check more assertions on it.
+
+{% tabs 'intercept-2' %}
+{% tab 'Scala 2 and 3' %}
+```scala
+val exception = intercept[NoSuchFileException](os.read(missingFile))
+assert(clue(exception.getMessage).contains("missing.txt"))
+```
+{% endtab %}
+{% endtabs %}
+
+You can also use the more concise `interceptMessage` method to test the exception and its message in a single assertion.
+Learn more about it in the [MUnit documentation](https://scalameta.org/munit/docs/assertions.html#interceptmessage).
diff --git a/_overviews/toolkit/testing-intro.md b/_overviews/toolkit/testing-intro.md
new file mode 100644
index 0000000000..8aa5021b76
--- /dev/null
+++ b/_overviews/toolkit/testing-intro.md
@@ -0,0 +1,21 @@
+---
+title: Testing with MUnit
+type: chapter
+description: The introduction of the MUnit library
+num: 2
+previous-page: introduction
+next-page: testing-suite
+---
+
+MUnit is a lightweight testing library. It provides a single style for writing tests, a style that can be learned quickly.
+
+Despite its simplicity, MUnit has useful features such as:
+- assertions to verify the behavior of the program
+- fixtures to ensure that the tests have access to all the necessary resources
+- asynchronous support, for testing concurrent and distributed applications.
+
+MUnit produces actionable error reports, with diff and source location, to help you quickly understand failures.
+
+Testing is essential for any software development process because it helps catch bugs early, improves code quality and facilitates collaboration.
+
+{% include markdown.html path="_markdown/install-munit.md" %}
diff --git a/_overviews/toolkit/testing-resources.md b/_overviews/toolkit/testing-resources.md
new file mode 100644
index 0000000000..9e0d45f51f
--- /dev/null
+++ b/_overviews/toolkit/testing-resources.md
@@ -0,0 +1,101 @@
+---
+title: How to manage the resources of a test?
+type: section
+description: Describe the functional fixtures
+num: 8
+previous-page: testing-asynchronous
+next-page: testing-what-else
+---
+
+{% include markdown.html path="_markdown/install-munit.md" %}
+
+## `FunFixture`
+
+In MUnit, we use functional fixtures to manage resources in a concise and safe way.
+A `FunFixture` creates one resource for each test, ensuring that each test runs in isolation from the others.
+
+In a test suite, you can define and use a `FunFixture` as follows:
+
+{% tabs 'resources-1' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc
+class FileTests extends munit.FunSuite {
+ val usingTempFile: FunFixture[os.Path] = FunFixture(
+ setup = _ => os.temp(prefix = "file-tests"),
+ teardown = tempFile => os.remove(tempFile)
+ )
+ usingTempFile.test("overwrite on file") { tempFile =>
+ os.write.over(tempFile, "Hello, World!")
+ val obtained = os.read(tempFile)
+ assertEquals(obtained, "Hello, World!")
+ }
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+class FileTests extends munit.FunSuite:
+ val usingTempFile: FunFixture[os.Path] = FunFixture(
+ setup = _ => os.temp(prefix = "file-tests"),
+ teardown = tempFile => os.remove(tempFile)
+ )
+ usingTempFile.test("overwrite on file") { tempFile =>
+ os.write.over(tempFile, "Hello, World!")
+ val obtained = os.read(tempFile)
+ assertEquals(obtained, "Hello, World!")
+ }
+```
+{% endtab %}
+{% endtabs %}
+
+`usingTempFile` is a fixture of type `FunFixture[os.Path]`.
+It contains two functions:
+ - The `setup` function, of type `TestOptions => os.Path`, creates a new temporary file.
+ - The `teardown` function, of type `os.Path => Unit`, deletes this temporary file.
+
+We use the `usingTempFile` fixture to define a test that needs a temporary file.
+Notice that the body of the test takes a `tempFile`, of type `os.Path`, as parameter.
+The fixture automatically creates this temporary file, calls its `setup` function, and cleans it up after the test by calling `teardown`.
+
+In the example, we used a fixture to manage a temporary file.
+In general, fixtures can manage other kinds of resources, such as a temporary folder, a temporary table in a database, a connection to a local server, and so on.
+
+## Composing `FunFixture`s
+
+In some tests, you may need more than one resource.
+You can use `FunFixture.map2` to compose two functional fixtures into one.
+
+{% tabs 'resources-2' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+val using2TempFiles: FunFixture[(os.Path, os.Path)] =
+ FunFixture.map2(usingTempFile, usingTempFile)
+
+using2TempFiles.test("merge two files") {
+ (file1, file2) =>
+ // body of the test
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+val using2TempFiles: FunFixture[(os.Path, os.Path)] =
+ FunFixture.map2(usingTempFile, usingTempFile)
+
+using2TempFiles.test("merge two files"):
+ (file1, file2) =>
+ // body of the test
+```
+{% endtab %}
+{% endtabs %}
+
+Using `FunFixture.map2` on a `FunFixture[A]` and a `FunFixture[B]` returns a `FunFixture[(A, B)]`.
+
+## Other fixtures
+
+`FunFixture` is the recommended type of fixture because:
+- it is explicit: each test declares the resource they need,
+- it is safe to use: each test uses its own resource in isolation.
+
+For more flexibility, `MUnit` contains other types of fixtures: the reusable fixture, the ad-hoc fixture and the asynchronous fixture.
+Learn more about them in the [MUnit documentation](https://scalameta.org/munit/docs/fixtures.html).
diff --git a/_overviews/toolkit/testing-run-only.md b/_overviews/toolkit/testing-run-only.md
new file mode 100644
index 0000000000..af6ba02cd7
--- /dev/null
+++ b/_overviews/toolkit/testing-run-only.md
@@ -0,0 +1,106 @@
+---
+title: How to run a single test?
+type: section
+description: About testOnly in the build tool and .only in MUnit
+num: 5
+previous-page: testing-run
+next-page: testing-exceptions
+---
+
+{% include markdown.html path="_markdown/install-munit.md" %}
+
+## Running a single test suite
+
+{% tabs munit-unit-test-only class=tabs-build-tool %}
+{% tab 'Scala CLI' %}
+To run a single `example.MyTests` suite with Scala CLI, use the `--test-only` option of the `test` command.
+```
+scala-cli test example --test-only example.MyTests
+```
+
+{% endtab %}
+{% tab 'sbt' %}
+To run a single `example.MyTests` suite in sbt, use the `testOnly` task:
+```
+sbt:example> example/testOnly example.MyTests
+```
+{% endtab %}
+{% tab 'Mill' %}
+To run a single `example.MyTests` suite in Mill, use the `testOnly` task:
+```
+./mill example.test.testOnly example.MyTests
+```
+{% endtab %}
+{% endtabs %}
+
+## Running a single test in a test suite
+
+Within a test suite file, you can select individual tests to run by temporarily appending `.only`, e.g.
+
+{% tabs 'only-demo' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc
+class MathSuite extends munit.FunSuite {
+ test("addition") {
+ assert(1 + 1 == 2)
+ }
+ test("multiplication".only) {
+ assert(3 * 7 == 21)
+ }
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+class MathSuite extends munit.FunSuite:
+ test("addition"):
+ assert(1 + 1 == 2)
+ test("multiplication".only):
+ assert(3 * 7 == 21)
+```
+{% endtab %}
+{% endtabs %}
+
+In the above example, only the `"multiplication"` tests will run (i.e. `"addition"` is ignored).
+This is useful to quickly debug a specific test in a suite.
+
+## Alternative: excluding specific tests
+
+You can exclude specific tests from running by appending `.ignore` to the test name.
+For example the following ignores the `"addition"` test, and run all the others:
+
+{% tabs 'ignore-demo' class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala mdoc:reset
+class MathSuite extends munit.FunSuite {
+ test("addition".ignore) {
+ assert(1 + 1 == 2)
+ }
+ test("multiplication") {
+ assert(3 * 7 == 21)
+ }
+ test("remainder") {
+ assert(13 % 5 == 3)
+ }
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+class MathSuite extends munit.FunSuite:
+ test("addition".ignore):
+ assert(1 + 1 == 2)
+ test("multiplication"):
+ assert(3 * 7 == 21)
+ test("remainder"):
+ assert(13 % 5 == 3)
+```
+{% endtab %}
+{% endtabs %}
+
+## Use tags to group tests, and run specific tags
+
+MUnit lets you group and run tests across suites by tags, which are textual labels.
+[The MUnit docs][munit-tags] have instructions on how to do this.
+
+[munit-tags]: https://scalameta.org/munit/docs/filtering.html#include-and-exclude-tests-based-on-tags
diff --git a/_overviews/toolkit/testing-run.md b/_overviews/toolkit/testing-run.md
new file mode 100644
index 0000000000..cb3fde0ade
--- /dev/null
+++ b/_overviews/toolkit/testing-run.md
@@ -0,0 +1,91 @@
+---
+title: How to run tests?
+type: section
+description: Running the MUnit tests
+num: 4
+previous-page: testing-suite
+next-page: testing-run-only
+---
+
+{% include markdown.html path="_markdown/install-munit.md" %}
+
+## Running the tests
+
+You can run all of your test suites with a single command.
+
+{% tabs munit-unit-test-4 class=tabs-build-tool %}
+{% tab 'Scala CLI' %}
+Using Scala CLI, the following command runs all the tests in the folder `example`:
+```
+scala-cli test example
+# Compiling project (test, Scala 3.2.1, JVM)
+# Compiled project (test, Scala 3.2.1, JVM)
+# MyTests:
+# + sum of two integers 0.009s
+```
+{% endtab %}
+{% tab 'sbt' %}
+In the sbt shell, the following command runs all the tests of the project `example`:
+```
+sbt:example> example/test
+# MyTests:
+# + sum of two integers 0.006s
+# [info] Passed: Total 1, Failed 0, Errors 0, Passed 1
+# [success] Total time: 0 s, completed Nov 11, 2022 12:54:08 PM
+```
+{% endtab %}
+{% tab 'Mill' %}
+In Mill, the following command runs all the tests of the module `example`:
+```
+./mill example.test.test
+# [71/71] example.test.test
+# MyTests:
+# + sum of two integers 0.008s
+```
+{% endtab %}
+{% endtabs %}
+
+The test report, printed in the console, shows the status of each test.
+The `+` symbol before a test name shows that the test passed successfully.
+
+Add and run a failing test to see how a failure looks:
+
+{% tabs assertions-1 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+test("failing test") {
+ val obtained = 2 + 3
+ val expected = 4
+ assertEquals(obtained, expected)
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+test("failing test"):
+ val obtained = 2 + 3
+ val expected = 4
+ assertEquals(obtained, expected)
+```
+{% endtab %}
+{% endtabs %}
+
+```
+# MyTests:
+# + sum of two integers 0.008s
+# ==> X MyTests.failing test 0.015s munit.ComparisonFailException: ./example/MyTests.test.scala:13
+# 12: val expected = 4
+# 13: assertEquals(obtained, expected)
+# 14: }
+# values are not the same
+# => Obtained
+# 5
+# => Diff (- obtained, + expected)
+# -5
+# +4
+# at munit.Assertions.failComparison(Assertions.scala:274)
+```
+
+The line starting with `==> X` indicates that the test named `failing test` fails.
+The following lines show where and how it failed.
+Here it shows that the obtained value is 5, where 4 was expected.
diff --git a/_overviews/toolkit/testing-suite.md b/_overviews/toolkit/testing-suite.md
new file mode 100644
index 0000000000..8b17de43a6
--- /dev/null
+++ b/_overviews/toolkit/testing-suite.md
@@ -0,0 +1,128 @@
+---
+title: How to write tests?
+type: section
+description: The basics of writing a test suite with MUnit
+num: 3
+previous-page: testing-intro
+next-page: testing-run
+---
+
+{% include markdown.html path="_markdown/install-munit.md" %}
+
+## Writing a test suite
+
+A group of tests in a single class is called a test class or test suite.
+
+Each test suite validates a particular component or feature of the software.
+Typically we define one test suite for each source file or class that we want to test.
+
+{% tabs munit-unit-test-2 class=tabs-build-tool %}
+{% tab 'Scala CLI' %}
+In Scala CLI, the test file can live in the same folder as the actual code, but the name of the file must end with `.test.scala`.
+In the following, `MyTests.test.scala` is a test file.
+```
+example/
+├── MyApp.scala
+└── MyTests.test.scala
+```
+Other valid structures and conventions are described in the [Scala CLI documentation](https://scala-cli.virtuslab.org/docs/commands/test/#test-sources).
+{% endtab %}
+{% tab 'sbt' %}
+In sbt, test sources go in the `src/test/scala` folder.
+
+For instance, the following is the file structure of a project `example`:
+```
+example
+└── src
+ ├── main
+ │ └── scala
+ │ └── MyApp.scala
+ └── test
+ └── scala
+ └── MyTests.scala
+```
+{% endtab %}
+{% tab 'Mill' %}
+In Mill, test sources go in the `test/src` folder.
+
+For instance, the following is the file structure of a module `example`:
+```
+example
+└── src
+| └── MyApp.scala
+└── test
+ └── src
+ └── MyTests.scala
+```
+{% endtab %}
+{% endtabs %}
+
+In the test source file, define a suite containing a single test:
+
+{% tabs munit-unit-test-3 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+package example
+
+class MyTests extends munit.FunSuite {
+ test("sum of two integers") {
+ val obtained = 2 + 2
+ val expected = 4
+ assertEquals(obtained, expected)
+ }
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+package example
+
+class MyTests extends munit.FunSuite:
+ test("sum of two integers"):
+ val obtained = 2 + 2
+ val expected = 4
+ assertEquals(obtained, expected)
+```
+{% endtab %}
+{% endtabs %}
+
+A test suite is a Scala class that extends `munit.FunSuite`.
+It contains one or more tests, each defined by a call to the `test` method.
+
+In the previous example, we have a single test `"sum of integers"` that checks that `2 + 2` equals `4`.
+We use the assertion method `assertEquals` to check that two values are equal.
+The test passes if all the assertions are correct and fails otherwise.
+
+## Assertions
+
+It is important to use assertions in each and every test to describe what to check.
+The main assertion methods in MUnit are:
+- `assertEquals` to check that what you obtain is equal to what you expected
+- `assert` to check a boolean condition
+
+The following is an example of a test that use `assert` to check a boolean condition on a list.
+
+{% tabs assertions-1 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+test("all even numbers") {
+ val input: List[Int] = List(1, 2, 3, 4)
+ val obtainedResults: List[Int] = input.map(_ * 2_)
+ // check that obtained values are all even numbers
+ assert(obtainedResults.forall(x => x % 2 == 0))
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+test("all even numbers"):
+ val input: List[Int] = List(1, 2, 3, 4)
+ val obtainedResults: List[Int] = input.map(_ * 2_)
+ // check that obtained values are all even numbers
+ assert(obtainedResults.forall(x => x % 2 == 0))
+```
+{% endtab %}
+{% endtabs %}
+
+MUnit contains more assertion methods that you can discover in its [documentation](https://scalameta.org/munit/docs/assertions.html):
+`assertNotEquals`, `assertNoDiff`, `fail`, and `compileErrors`.
diff --git a/_overviews/toolkit/testing-what-else.md b/_overviews/toolkit/testing-what-else.md
new file mode 100644
index 0000000000..305765ec30
--- /dev/null
+++ b/_overviews/toolkit/testing-what-else.md
@@ -0,0 +1,83 @@
+---
+title: What else can MUnit do?
+type: section
+description: A incomplete list of features of MUnit
+num: 9
+previous-page: testing-resources
+next-page: os-intro
+---
+
+{% include markdown.html path="_markdown/install-munit.md" %}
+
+## Adding clues to get better error report
+
+Use `clue` inside an `assert` to a get a better error report when the assertion fails.
+
+{% tabs clues %}
+{% tab 'Scala 2 and 3' %}
+```scala
+assert(clue(List(a).head) > clue(b))
+// munit.FailException: assertion failed
+// Clues {
+// List(a).head: Int = 1
+// b: Int = 2
+// }
+```
+{% endtab %}
+{% endtabs %}
+
+Learn more about clues in the [MUnit documentation](https://scalameta.org/munit/docs/assertions.html#assert).
+
+## Writing environment-specific tests
+
+Use `assume` to write environment-specific tests.
+`assume` can contain a boolean condition. You can check the operating system, the Java version, a Java property, an environment variable, or anything else.
+A test is skipped if one of its assumptions isn't met.
+
+{% tabs assumption class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+import scala.util.Properties
+
+test("home directory") {
+ assume(Properties.isLinux, "this test runs only on Linux")
+ assert(os.home.toString.startsWith("/home/"))
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+import scala.util.Properties
+
+test("home directory"):
+ assume(Properties.isLinux, "this test runs only on Linux")
+ assert(os.home.toString.startsWith("/home/"))
+```
+{% endtab %}
+{% endtabs %}
+
+Learn more about filtering tests in the [MUnit documentation](https://scalameta.org/munit/docs/filtering.html).
+
+## Tagging flaky tests
+
+You can tag a test with `flaky` to mark it as being flaky.
+Flaky tests can be skipped by setting the `MUNIT_FLAKY_OK` environment variable to `true`.
+
+{% tabs flaky class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+test("requests".flaky) {
+ // I/O heavy tests that sometimes fail
+}
+```
+{% endtab %}
+{% tab 'Scala 3' %}
+```scala
+test("requests".flaky):
+ // I/O heavy tests that sometimes fail
+```
+{% endtab %}
+{% endtabs %}
+
+Learn more about flaky tests in the [MUnit documentation](https://scalameta.org/munit/docs/tests.html#tag-flaky-tests)
+
diff --git a/_overviews/tutorials/binary-compatibility-for-library-authors.md b/_overviews/tutorials/binary-compatibility-for-library-authors.md
index 47261847d6..a6d358ed14 100644
--- a/_overviews/tutorials/binary-compatibility-for-library-authors.md
+++ b/_overviews/tutorials/binary-compatibility-for-library-authors.md
@@ -182,21 +182,39 @@ To achieve that, follow this pattern:
* define a private `unapply` function in the companion object (note that by doing that the case class loses the ability to be used as an extractor in match expressions)
* for all the fields, define `withXXX` methods on the case class that create a new instance with the respective field changed (you can use the private `copy` method to implement them)
* create a public constructor by defining an `apply` method in the companion object (it can use the private constructor)
+ * in Scala 2, you have to add the compiler option `-Xsource:3`
Example:
-{% tabs case_class_compat_1 %}
-{% tab 'Scala 3 Only' %}
+{% tabs case_class_compat_1 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+~~~ scala
+// Mark the primary constructor as private
+case class Person private (name: String, age: Int) {
+ // Create withXxx methods for every field, implemented by using the (private) copy method
+ def withName(name: String): Person = copy(name = name)
+ def withAge(age: Int): Person = copy(age = age)
+}
+
+object Person {
+ // Create a public constructor (which uses the private primary constructor)
+ def apply(name: String, age: Int) = new Person(name, age)
+ // Make the extractor private
+ private def unapply(p: Person): Some[Person] = Some(p)
+}
+~~~
+{% endtab %}
+{% tab 'Scala 3' %}
```scala
// Mark the primary constructor as private
case class Person private (name: String, age: Int):
- // Create withXxx methods for every field, implemented by using the copy method
+ // Create withXxx methods for every field, implemented by using the (private) copy method
def withName(name: String): Person = copy(name = name)
def withAge(age: Int): Person = copy(age = age)
object Person:
- // Create a public constructor (which uses the primary constructor)
+ // Create a public constructor (which uses the private primary constructor)
def apply(name: String, age: Int): Person = new Person(name, age)
// Make the extractor private
private def unapply(p: Person) = p
@@ -239,8 +257,21 @@ Later in time, you can amend the original case class definition to, say, add an
* update the public `apply` method in the companion object to initialize all the fields,
* tell MiMa to [ignore](https://github.com/lightbend/mima#filtering-binary-incompatibilities) changes to the class constructor. This step is necessary because MiMa does not yet ignore changes in private class constructor signatures (see [#738](https://github.com/lightbend/mima/issues/738)).
-{% tabs case_class_compat_4 %}
-{% tab 'Scala 3 Only' %}
+{% tabs case_class_compat_4 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+~~~ scala
+case class Person private (name: String, age: Int, address: Option[String]) {
+ ...
+ def withAddress(address: Option[String]) = copy(address = address)
+}
+
+object Person {
+ // Update the public constructor to also initialize the address field
+ def apply(name: String, age: Int): Person = new Person(name, age, None)
+}
+~~~
+{% endtab %}
+{% tab 'Scala 3' %}
```scala
case class Person private (name: String, age: Int, address: Option[String]):
...
@@ -295,8 +326,19 @@ A regular case class not following this pattern would break its usage, because b
Optionally, you can also add overloads of the `apply` method in the companion object to initialize more fields
in one call. In our example, we can add an overload that also initializes the `address` field:
-{% tabs case_class_compat_7 %}
-{% tab 'Scala 3 Only' %}
+{% tabs case_class_compat_7 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+~~~ scala
+object Person {
+ // Original public constructor
+ def apply(name: String, age: Int): Person = new Person(name, age, None)
+ // Additional constructor that also sets the address
+ def apply(name: String, age: Int, address: String): Person =
+ new Person(name, age, Some(address))
+}
+~~~
+{% endtab %}
+{% tab 'Scala 3' %}
~~~ scala
object Person:
// Original public constructor
diff --git a/_plugins/jekyll-tabs-lib/jekyll-tabs-4scala.rb b/_plugins/jekyll-tabs-lib/jekyll-tabs-4scala.rb
index eee013d138..b592fcc7f9 100644
--- a/_plugins/jekyll-tabs-lib/jekyll-tabs-4scala.rb
+++ b/_plugins/jekyll-tabs-lib/jekyll-tabs-4scala.rb
@@ -4,6 +4,7 @@ module Jekyll
module Tabs
ScalaVersions = ['Scala 2', 'Scala 3']
+ BuildTools = ['Scala CLI', 'sbt', 'Mill']
def self.unquote(string)
string.gsub(/^['"]|['"]$/, '')
@@ -25,13 +26,11 @@ def initialize(block_name, markup, tokens)
if markup =~ SYNTAX
@name = Tabs::unquote($1)
@css_classes = ""
- @is_scala_tabs = false
+ @tab_class = ""
if $2
css_class = Tabs::unquote($2)
css_class.strip!
- if css_class == "tabs-scala-version"
- @is_scala_tabs = true
- end
+ @tab_class = css_class
# append $2 to @css_classes
@css_classes = " #{css_class}"
end
@@ -56,36 +55,36 @@ def render(context)
seenTabs = []
- def joinScalaVersions()
- Tabs::ScalaVersions.to_a.map{|item| "'#{item}'"}.join(", ")
+ def joinTabs(tabs)
+ tabs.to_a.map{|item| "'#{item}'"}.join(", ")
end
- def errorNonScalaVersion(tab)
+ def errorInvalidTab(tab, expectedTabs)
SyntaxError.new(
- "Scala version tab label '#{tab.label}' is not valid for tabs '#{@name}' with " +
- "class=tabs-scala-version. Valid tab labels are: #{joinScalaVersions()}")
+ "Tab label '#{tab.label}' is not valid for tabs '#{@name}' with " +
+ "class=#{@tab_class}. Valid tab labels are: #{joinTabs(expectedTabs)}")
end
- def errorScalaVersionWithoutClass(tab)
+ def errorTabWithoutClass(tab, tabClass)
SyntaxError.new(
- "Scala version tab label '#{tab.label}' is not valid for tabs '#{@name}' without " +
- "class=tabs-scala-version")
+ "Tab label '#{tab.label}' is not valid for tabs '#{@name}' without " +
+ "class=#{tabClass}")
end
- def errorMissingScalaVersion()
+ def errorMissingTab(expectedTabs)
SyntaxError.new(
- "Tabs '#{@name}' with class=tabs-scala-version must have exactly the following " +
- "tab labels: #{joinScalaVersions()}")
+ "Tabs '#{@name}' with class=#{@tab_class} must have exactly the following " +
+ "tab labels: #{joinTabs(expectedTabs)}")
end
def errorDuplicateTab(tab)
SyntaxError.new("Duplicate tab label '#{tab.label}' in tabs '#{@name}'")
end
- def errorScalaVersionDefault(tab)
+ def errorTabDefault(tab)
SyntaxError.new(
- "Scala version tab label '#{tab.label}' should not be default for tabs '#{@name}' " +
- "with class=tabs-scala-version")
+ "Tab label '#{tab.label}' should not be default for tabs '#{@name}' " +
+ "with class=#{@tab_class}")
end
allTabs.each do | tab |
@@ -97,25 +96,34 @@ def errorScalaVersionDefault(tab)
foundDefault = true
end
- isScalaTab = Tabs::ScalaVersions.include? tab.label
-
- if @is_scala_tabs
- if !isScalaTab
- raise errorNonScalaVersion(tab)
- elsif tab.defaultTab
- raise errorScalaVersionDefault(tab)
+ def checkTab(tab, tabClass, expectedTabs, raiseIfMissingClass)
+ isValid = expectedTabs.include? tab.label
+ if @tab_class == tabClass
+ if !isValid
+ raise errorInvalidTab(tab, expectedTabs)
+ elsif tab.defaultTab
+ raise errorTabDefault(tab)
+ end
+ elsif raiseIfMissingClass and isValid
+ raise errorTabWithoutClass(tab, tabClass)
end
- elsif !@is_scala_tabs and isScalaTab
- raise errorScalaVersionWithoutClass(tab)
end
+
+ checkTab(tab, "tabs-scala-version", Tabs::ScalaVersions, true)
+ checkTab(tab, "tabs-build-tool", Tabs::BuildTools, false)
end
- if @is_scala_tabs and seenTabs != Tabs::ScalaVersions
- raise errorMissingScalaVersion()
+ def checkExhaustivity(seenTabs, tabClass, expectedTabs)
+ if @tab_class == tabClass and seenTabs != expectedTabs
+ raise errorMissingTab(expectedTabs)
+ end
end
+ checkExhaustivity(seenTabs, "tabs-scala-version", Tabs::ScalaVersions)
+ checkExhaustivity(seenTabs, "tabs-build-tool", Tabs::BuildTools)
+
if !foundDefault and allTabs.length > 0
- if @is_scala_tabs
+ if @tab_class == "tabs-scala-version"
# set last tab to default ('Scala 3')
allTabs[-1].defaultTab = true
else
diff --git a/_plugins/mdoc_replace.rb b/_plugins/mdoc_replace.rb
index 0b62828227..a4d8bf6e11 100644
--- a/_plugins/mdoc_replace.rb
+++ b/_plugins/mdoc_replace.rb
@@ -12,6 +12,7 @@ def output_ext(ext)
end
def convert(content)
+ content = content.gsub("```scala mdoc:compile-only\n", "```scala\n")
content = content.gsub("```scala mdoc:fail\n", "```scala\n")
content = content.gsub("```scala mdoc:crash\n", "```scala\n")
content = content.gsub("```scala mdoc:nest\n", "```scala\n")
diff --git a/_ru/overviews/collections-2.13/overview.md b/_ru/overviews/collections-2.13/overview.md
index 361d8f5551..6eb7087c63 100644
--- a/_ru/overviews/collections-2.13/overview.md
+++ b/_ru/overviews/collections-2.13/overview.md
@@ -41,7 +41,7 @@ language: ru
scala.collection.immutable.List // Полное объявление
scala.List // объявление через псевдоним
- List // тк scala._ всегда автоматически импортируется
+ List // т.к. scala._ всегда автоматически импортируется
// можно просто указать имя коллекции
Другие псевдонимы для типов
diff --git a/_ru/scala3/book/collections-classes.md b/_ru/scala3/book/collections-classes.md
new file mode 100644
index 0000000000..53f169249f
--- /dev/null
+++ b/_ru/scala3/book/collections-classes.md
@@ -0,0 +1,971 @@
+---
+layout: multipage-overview
+title: Типы коллекций
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: section
+description: На этой странице представлены общие типы коллекций Scala 3 и некоторые из их методов.
+language: ru
+num: 37
+previous-page: collections-intro
+next-page: collections-methods
+---
+
+
+На этой странице показаны общие коллекции Scala 3 и сопутствующие им методы.
+Scala поставляется с большим количеством типов коллекций, на изучение которых может уйти время,
+поэтому желательно начать с нескольких из них, а затем использовать остальные по мере необходимости.
+Точно так же у каждого типа коллекции есть десятки методов, облегчающих разработку,
+поэтому лучше начать изучение лишь с небольшого количества.
+
+В этом разделе представлены наиболее распространенные типы и методы коллекций,
+которые вам понадобятся для начала работы.
+
+В конце этого раздела представлены дополнительные ссылки, для более глубокого изучения коллекций.
+
+## Три основные категории коллекций
+
+Для коллекций Scala можно выделить три основные категории:
+
+- **Последовательности** (**Sequences**/**Seq**) представляют собой последовательный набор элементов
+ и могут быть _индексированными_ (как массив) или _линейными_ (как связанный список)
+- **Мапы** (**Maps**) содержат набор пар ключ/значение, например Java `Map`, Python dictionary или Ruby `Hash`
+- **Множества** (**Sets**) — это неупорядоченный набор уникальных элементов
+
+Все они являются базовыми типами и имеют подтипы подходящие под конкретные задачи,
+таких как параллелизм (concurrency), кэширование (caching) и потоковая передача (streaming).
+В дополнение к этим трем основным категориям существуют и другие полезные типы коллекций,
+включая диапазоны (ranges), стеки (stacks) и очереди (queues).
+
+
+### Иерархия коллекций
+
+В качестве краткого обзора следующие три рисунка показывают иерархию классов и трейтов в коллекциях Scala.
+
+На первом рисунке показаны типы коллекций в пакете _scala.collection_.
+Все это высокоуровневые абстрактные классы или трейты, которые обычно имеют _неизменяемые_ и _изменяемые_ реализации.
+
+![General collection hierarchy][collections1]
+
+На этом рисунке показаны все коллекции в пакете _scala.collection.immutable_:
+
+![Immutable collection hierarchy][collections2]
+
+А на этом рисунке показаны все коллекции в пакете _scala.collection.mutable_:
+
+![Mutable collection hierarchy][collections3]
+
+В следующих разделах представлены некоторые из распространенных типов.
+
+## Общие коллекции
+
+Основные коллекции, используемые чаще всего:
+
+| Тип коллекции | Неизменяемая | Изменяемая | Описание |
+|----------------|--------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `List` | ✓ | | Линейная неизменяемая последовательность (связный список) |
+| `Vector` | ✓ | | Индексированная неизменяемая последовательность |
+| `LazyList` | ✓ | | Ленивый неизменяемый связанный список, элементы которого вычисляются только тогда, когда они необходимы; подходит для больших или бесконечных последовательностей. |
+| `ArrayBuffer` | | ✓ | Подходящий тип для изменяемой индексированной последовательности |
+| `ListBuffer` | | ✓ | Используется, когда вам нужен изменяемый список; обычно преобразуется в `List` |
+| `Map` | ✓ | ✓ | Итерируемая коллекция, состоящая из пар ключей и значений |
+| `Set` | ✓ | ✓ | Итерируемая коллекция без повторяющихся элементов |
+
+Как показано, `Map` и `Set` бывают как изменяемыми, так и неизменяемыми.
+
+Основы каждого типа демонстрируются в следующих разделах.
+
+> В Scala _буфер_ (_buffer_), такой как `ArrayBuffer` или `ListBuffer`, представляет собой последовательность,
+> которая может увеличиваться и уменьшаться.
+
+### Примечание о неизменяемых коллекциях
+
+В последующих разделах всякий раз, когда используется слово _immutable_, можно с уверенностью сказать,
+что тип предназначен для использования в стиле _функционального программирования_ (ФП).
+С помощью таких типов коллекция не меняется,
+а при вызове функциональных методов возвращается новый результат - новая коллекция.
+
+## Выбор последовательности
+
+При выборе _последовательности_ (последовательной коллекции элементов) нужно руководствоваться двумя основными вопросами:
+
+- должна ли последовательность индексироваться (как массив), обеспечивая быстрый доступ к любому элементу,
+ или она должна быть реализована как линейный связанный список?
+- необходима изменяемая или неизменяемая коллекция?
+
+Рекомендуемые универсальные последовательности:
+
+| Тип\Категория | Неизменяемая | Изменяемая |
+|-----------------------------|--------------|---------------|
+| индексируемая | `Vector` | `ArrayBuffer` |
+| линейная (связанный список) | `List` | `ListBuffer` |
+
+Например, если нужна неизменяемая индексированная коллекция, в общем случае следует использовать `Vector`.
+И наоборот, если нужна изменяемая индексированная коллекция, используйте `ArrayBuffer`.
+
+> `List` и `Vector` часто используются при написании кода в функциональном стиле.
+> `ArrayBuffer` обычно используется при написании кода в императивном стиле.
+> `ListBuffer` используется тогда, когда стили смешиваются, например, при создании списка.
+
+Следующие несколько разделов кратко демонстрируют типы `List`, `Vector` и `ArrayBuffer`.
+
+
+## `List`
+
+[List](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html)
+представляет собой линейную неизменяемую последовательность.
+Каждый раз, когда в список добавляются или удаляются элементы, по сути создается новый список из существующего.
+
+### Создание списка
+
+`List` можно создать различными способами:
+
+{% tabs list-creation %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val ints = List(1, 2, 3)
+val names = List("Joel", "Chris", "Ed")
+
+// другой путь создания списка List
+val namesAgain = "Joel" :: "Chris" :: "Ed" :: Nil
+```
+{% endtab %}
+
+{% endtabs %}
+
+При желании тип списка можно объявить, хотя обычно в этом нет необходимости:
+
+{% tabs list-type %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val ints: List[Int] = List(1, 2, 3)
+val names: List[String] = List("Joel", "Chris", "Ed")
+```
+{% endtab %}
+
+{% endtabs %}
+
+Одно исключение — когда в коллекции смешанные типы; в этом случае тип желательно указывать явно:
+
+{% tabs list-mixed-types class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+val things: List[Any] = List(1, "two", 3.0)
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+val things: List[String | Int | Double] = List(1, "two", 3.0) // с типами объединения
+val thingsAny: List[Any] = List(1, "two", 3.0) // с Any
+```
+{% endtab %}
+
+{% endtabs %}
+
+### Добавление элементов в список
+
+Поскольку `List` неизменяем, в него нельзя добавлять новые элементы.
+Вместо этого создается новый список с добавленными к существующему списку элементами.
+Например, учитывая этот `List`:
+
+{% tabs adding-elements-init %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = List(1, 2, 3)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Для _добавления_ (_prepend_) к началу списка одного элемента используется метод `::`, для добавления нескольких — `:::`, как показано здесь:
+
+{% tabs adding-elements-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val b = 0 :: a // List(0, 1, 2, 3)
+val c = List(-1, 0) ::: a // List(-1, 0, 1, 2, 3)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Также можно _добавить_ (_append_) элементы в конец `List`, но, поскольку `List` является односвязным,
+следует добавлять к нему элементы только в начало;
+добавление элементов в конец списка — относительно медленная операция,
+особенно при работе с большими последовательностями.
+
+> Совет: если необходимо добавлять к неизменяемой последовательности элементы в начало и конец, используйте `Vector`.
+
+Поскольку `List` является связанным списком,
+крайне нежелательно пытаться получить доступ к элементам больших списков по значению их индекса.
+Например, если есть `List` с миллионом элементов, доступ к такому элементу, как `myList(999_999)`,
+займет относительно много времени, потому что этот запрос должен пройти почти через все элементы.
+Если есть большая коллекция и необходимо получать доступ к элементам по их индексу, то
+вместо `List` используйте `Vector` или `ArrayBuffer`.
+
+### Как запомнить названия методов
+
+В методах Scala символ `:` представляет сторону, на которой находится последовательность,
+поэтому, когда используется метод `+:`, список нужно указывать справа:
+
+{% tabs list-prepending %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+0 +: a
+```
+{% endtab %}
+
+{% endtabs %}
+
+Аналогично, если используется `:+`, список должен быть слева:
+
+{% tabs list-appending %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+a :+ 4
+```
+{% endtab %}
+
+{% endtabs %}
+
+Хорошей особенностью таких символических имен у методов является то, что они стандартизированы.
+
+Те же имена методов используются с другими неизменяемыми последовательностями, такими как `Seq` и `Vector`.
+Также можно использовать несимволические имена методов для добавления элементов в начало (`a.prepended(4)`)
+или конец (`a.appended(4)`).
+
+### Как пройтись по списку
+
+Представим, что есть `List` имён:
+
+{% tabs list-loop-init %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val names = List("Joel", "Chris", "Ed")
+```
+{% endtab %}
+
+{% endtabs %}
+
+Напечатать каждое имя можно следующим способом:
+
+{% tabs list-loop-example class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+for (name <- names) println(name)
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+for name <- names do println(name)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Вот как это выглядит в REPL:
+
+{% tabs list-loop-repl class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+scala> for (name <- names) println(name)
+Joel
+Chris
+Ed
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+scala> for name <- names do println(name)
+Joel
+Chris
+Ed
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+Преимуществом использования выражений вида `for` с коллекциями в том, что Scala стандартизирован,
+и один и тот же подход работает со всеми последовательностями,
+включая `Array`, `ArrayBuffer`, `List`, `Seq`, `Vector`, `Map`, `Set` и т.д.
+
+### Немного истории
+
+Список Scala подобен списку из языка программирования [Lisp](https://en.wikipedia.org/wiki/Lisp_(programming_language)),
+который был впервые представлен в 1958 году.
+Действительно, в дополнение к привычному способу создания списка:
+
+{% tabs list-history-init %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val ints = List(1, 2, 3)
+```
+{% endtab %}
+
+{% endtabs %}
+
+точно такой же список можно создать следующим образом:
+
+{% tabs list-history-init2 %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val list = 1 :: 2 :: 3 :: Nil
+```
+{% endtab %}
+
+{% endtabs %}
+
+REPL показывает, как это работает:
+
+{% tabs list-history-repl %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+scala> val list = 1 :: 2 :: 3 :: Nil
+list: List[Int] = List(1, 2, 3)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Это работает, потому что `List` — односвязный список, оканчивающийся элементом `Nil`,
+а `::` — это метод `List`, работающий как оператор “cons” в Lisp.
+
+
+### Отступление: LazyList
+
+Коллекции Scala также включают [LazyList](https://www.scala-lang.org/api/current/scala/collection/immutable/LazyList.html),
+который представляет собой _ленивый_ неизменяемый связанный список.
+Он называется «ленивым» — или нестрогим — потому что вычисляет свои элементы только тогда, когда они необходимы.
+
+Вы можете увидеть отложенное вычисление `LazyList` в REPL:
+
+{% tabs lazylist-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val x = LazyList.range(1, Int.MaxValue)
+x.take(1) // LazyList()
+x.take(5) // LazyList()
+x.map(_ + 1) // LazyList()
+```
+{% endtab %}
+
+{% endtabs %}
+
+Во всех этих примерах ничего не происходит.
+Действительно, ничего не произойдет, пока вы не заставите это произойти, например, вызвав метод `foreach`:
+
+{% tabs lazylist-evaluation-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+scala> x.take(1).foreach(println)
+1
+```
+{% endtab %}
+
+{% endtabs %}
+
+Дополнительные сведения об использовании, преимуществах и недостатках строгих и нестрогих (ленивых) коллекций
+см. в обсуждениях “строгих” и “нестрогих” на странице [Архитектура коллекции в Scala 2.13][strict].
+
+## Vector
+
+[Vector](https://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html) - это индексируемая неизменяемая последовательность.
+“Индексируемая” часть описания означает, что она обеспечивает произвольный доступ
+и обновление за практически постоянное время,
+поэтому можно быстро получить доступ к элементам `Vector` по значению их индекса,
+например, получить доступ к `listOfPeople(123_456_789)`.
+
+В общем, за исключением той разницы, что (а) `Vector` индексируется, а `List` - нет,
+и (б) `List` имеет метод `::`, эти два типа работают одинаково,
+поэтому мы быстро пробежимся по следующим примерам.
+
+Вот несколько способов создания `Vector`:
+
+{% tabs vector-creation %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val nums = Vector(1, 2, 3, 4, 5)
+
+val strings = Vector("one", "two")
+
+case class Person(name: String)
+val people = Vector(
+ Person("Bert"),
+ Person("Ernie"),
+ Person("Grover")
+)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Поскольку `Vector` неизменяем, в него нельзя добавить новые элементы.
+Вместо этого создается новая последовательность, с добавленными к существующему `Vector` в начало или в конец элементами.
+
+Например, так элементы добавляются в конец:
+
+{% tabs vector-appending %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = Vector(1,2,3) // Vector(1, 2, 3)
+val b = a :+ 4 // Vector(1, 2, 3, 4)
+val c = a ++ Vector(4, 5) // Vector(1, 2, 3, 4, 5)
+```
+{% endtab %}
+
+{% endtabs %}
+
+А так - в начало Vector-а:
+
+{% tabs vector-prepending %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = Vector(1,2,3) // Vector(1, 2, 3)
+val b = 0 +: a // Vector(0, 1, 2, 3)
+val c = Vector(-1, 0) ++: a // Vector(-1, 0, 1, 2, 3)
+```
+{% endtab %}
+
+{% endtabs %}
+
+В дополнение к быстрому произвольному доступу и обновлениям, `Vector` обеспечивает быстрое добавление в начало и конец.
+
+> Подробную информацию о производительности `Vector` и других коллекций
+> см. [в характеристиках производительности коллекций](https://docs.scala-lang.org/overviews/collections-2.13/performance-characteristics.html).
+
+Наконец, `Vector` в выражениях вида `for` используется точно так же, как `List`, `ArrayBuffer` или любая другая последовательность:
+
+{% tabs vector-loop class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+scala> val names = Vector("Joel", "Chris", "Ed")
+val names: Vector[String] = Vector(Joel, Chris, Ed)
+
+scala> for (name <- names) println(name)
+Joel
+Chris
+Ed
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+scala> val names = Vector("Joel", "Chris", "Ed")
+val names: Vector[String] = Vector(Joel, Chris, Ed)
+
+scala> for name <- names do println(name)
+Joel
+Chris
+Ed
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+## ArrayBuffer
+
+`ArrayBuffer` используется тогда, когда нужна изменяемая индексированная последовательность общего назначения.
+Поскольку `ArrayBuffer` индексирован, произвольный доступ к элементам выполняется быстро.
+
+### Создание ArrayBuffer
+
+Чтобы использовать `ArrayBuffer`, его нужно вначале импортировать:
+
+{% tabs arraybuffer-import %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+import scala.collection.mutable.ArrayBuffer
+```
+{% endtab %}
+
+{% endtabs %}
+
+Если необходимо начать с пустого `ArrayBuffer`, просто укажите его тип:
+
+{% tabs arraybuffer-creation %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+var strings = ArrayBuffer[String]()
+var ints = ArrayBuffer[Int]()
+var people = ArrayBuffer[Person]()
+```
+{% endtab %}
+
+{% endtabs %}
+
+Если известен примерный размер `ArrayBuffer`, его можно задать:
+
+{% tabs list-creation-with-size %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+// готов вместить 100 000 чисел
+val buf = new ArrayBuffer[Int](100_000)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Чтобы создать новый `ArrayBuffer` с начальными элементами,
+достаточно просто указать начальные элементы, как для `List` или `Vector`:
+
+{% tabs arraybuffer-init %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val nums = ArrayBuffer(1, 2, 3)
+val people = ArrayBuffer(
+ Person("Bert"),
+ Person("Ernie"),
+ Person("Grover")
+)
+```
+{% endtab %}
+
+{% endtabs %}
+
+### Добавление элементов в ArrayBuffer
+
+Новые элементы добавляются в `ArrayBuffer` с помощью методов `+=` и `++=`.
+Также можно использовать текстовый аналог: `append`, `appendAll`, `insert`, `insertAll`, `prepend` и `prependAll`.
+Вот несколько примеров с `+=` и `++=`:
+
+{% tabs arraybuffer-add %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val nums = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3)
+nums += 4 // ArrayBuffer(1, 2, 3, 4)
+nums ++= List(5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6)
+```
+{% endtab %}
+
+{% endtabs %}
+
+### Удаление элементов из ArrayBuffer
+
+`ArrayBuffer` является изменяемым,
+поэтому у него есть такие методы, как `-=`, `--=`, `clear`, `remove` и другие.
+Примеры с `-=` и `--=`:
+
+{% tabs arraybuffer-remove %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = ArrayBuffer.range('a', 'h') // ArrayBuffer(a, b, c, d, e, f, g)
+a -= 'a' // ArrayBuffer(b, c, d, e, f, g)
+a --= Seq('b', 'c') // ArrayBuffer(d, e, f, g)
+a --= Set('d', 'e') // ArrayBuffer(f, g)
+```
+{% endtab %}
+
+{% endtabs %}
+
+### Обновление элементов в ArrayBuffer
+
+Элементы в `ArrayBuffer` можно обновлять, либо переназначать:
+
+{% tabs arraybuffer-update %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = ArrayBuffer.range(1,5) // ArrayBuffer(1, 2, 3, 4)
+a(2) = 50 // ArrayBuffer(1, 2, 50, 4)
+a.update(0, 10) // ArrayBuffer(10, 2, 50, 4)
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+
+## Maps
+
+`Map` — это итерируемая коллекция, состоящая из пар ключей и значений.
+В Scala есть как изменяемые, так и неизменяемые типы `Map`.
+В этом разделе показано, как использовать _неизменяемый_ `Map`.
+
+### Создание неизменяемой Map
+
+Неизменяемая `Map` создается следующим образом:
+
+{% tabs map-init %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val states = Map(
+ "AK" -> "Alaska",
+ "AL" -> "Alabama",
+ "AZ" -> "Arizona"
+)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Перемещаться по элементам `Map` используя выражение `for` можно следующим образом:
+
+{% tabs map-loop class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+for ((k, v) <- states) println(s"key: $k, value: $v")
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+for (k, v) <- states do println(s"key: $k, value: $v")
+```
+{% endtab %}
+
+{% endtabs %}
+
+REPL показывает, как это работает:
+
+{% tabs map-repl class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+scala> for ((k, v) <- states) println(s"key: $k, value: $v")
+key: AK, value: Alaska
+key: AL, value: Alabama
+key: AZ, value: Arizona
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+scala> for (k, v) <- states do println(s"key: $k, value: $v")
+key: AK, value: Alaska
+key: AL, value: Alabama
+key: AZ, value: Arizona
+```
+{% endtab %}
+
+{% endtabs %}
+
+### Доступ к элементам Map
+
+Доступ к элементам `Map` осуществляется через указание в скобках значения ключа:
+
+{% tabs map-access-element %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val ak = states("AK") // ak: String = Alaska
+val al = states("AL") // al: String = Alabama
+```
+{% endtab %}
+
+{% endtabs %}
+
+На практике также используются такие методы, как `keys`, `keySet`, `keysIterator`, `for` выражения
+и функции высшего порядка, такие как `map`, для работы с ключами и значениями `Map`.
+
+### Добавление элемента в Map
+
+При добавлении элементов в неизменяемую мапу с помощью `+` и `++`, создается новая мапа:
+
+{% tabs map-add-element %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = Map(1 -> "one") // a: Map(1 -> one)
+val b = a + (2 -> "two") // b: Map(1 -> one, 2 -> two)
+val c = b ++ Seq(
+ 3 -> "three",
+ 4 -> "four"
+)
+// c: Map(1 -> one, 2 -> two, 3 -> three, 4 -> four)
+```
+{% endtab %}
+
+{% endtabs %}
+
+### Удаление элементов из Map
+
+Элементы удаляются с помощью методов `-` или `--`.
+В случае неизменяемой `Map` создается новый экземпляр, который нужно присвоить новой переменной:
+
+{% tabs map-remove-element %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = Map(
+ 1 -> "one",
+ 2 -> "two",
+ 3 -> "three",
+ 4 -> "four"
+)
+
+val b = a - 4 // b: Map(1 -> one, 2 -> two, 3 -> three)
+val c = a - 4 - 3 // c: Map(1 -> one, 2 -> two)
+```
+{% endtab %}
+
+{% endtabs %}
+
+### Обновление элементов в Map
+
+Чтобы обновить элементы на неизменяемой `Map`, используется метод `update` (или оператор `+`):
+
+{% tabs map-update-element %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = Map(
+ 1 -> "one",
+ 2 -> "two",
+ 3 -> "three"
+)
+
+val b = a.updated(3, "THREE!") // b: Map(1 -> one, 2 -> two, 3 -> THREE!)
+val c = a + (2 -> "TWO...") // c: Map(1 -> one, 2 -> TWO..., 3 -> three)
+```
+{% endtab %}
+
+{% endtabs %}
+
+### Перебор элементов в Map
+
+Элементы в `Map` можно перебрать с помощью выражения `for`, как и для остальных коллекций:
+
+{% tabs map-traverse class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+val states = Map(
+ "AK" -> "Alaska",
+ "AL" -> "Alabama",
+ "AZ" -> "Arizona"
+)
+
+for ((k, v) <- states) println(s"key: $k, value: $v")
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+val states = Map(
+ "AK" -> "Alaska",
+ "AL" -> "Alabama",
+ "AZ" -> "Arizona"
+)
+
+for (k, v) <- states do println(s"key: $k, value: $v")
+```
+{% endtab %}
+
+{% endtabs %}
+
+Существует _много_ способов работы с ключами и значениями на `Map`.
+Общие методы `Map` включают `foreach`, `map`, `keys` и `values`.
+
+В Scala есть много других специализированных типов `Map`,
+включая `CollisionProofHashMap`, `HashMap`, `LinkedHashMap`, `ListMap`, `SortedMap`, `TreeMap`, `WeakHashMap` и другие.
+
+
+## Работа с множествами
+
+Множество ([Set]({{site.baseurl}}/overviews/collections-2.13/sets.html)) - итерируемая коллекция без повторяющихся элементов.
+
+В Scala есть как изменяемые, так и неизменяемые типы `Set`.
+В этом разделе демонстрируется _неизменяемое_ множество.
+
+### Создание множества
+
+Создание нового пустого множества:
+
+{% tabs set-creation %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val nums = Set[Int]()
+val letters = Set[Char]()
+```
+{% endtab %}
+
+{% endtabs %}
+
+Создание множества с исходными данными:
+
+{% tabs set-init %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val nums = Set(1, 2, 3, 3, 3) // Set(1, 2, 3)
+val letters = Set('a', 'b', 'c', 'c') // Set('a', 'b', 'c')
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+### Добавление элементов в множество
+
+В неизменяемое множество новые элементы добавляются с помощью `+` и `++`, результат присваивается новой переменной:
+
+{% tabs set-add-element %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = Set(1, 2) // Set(1, 2)
+val b = a + 3 // Set(1, 2, 3)
+val c = b ++ Seq(4, 1, 5, 5) // HashSet(5, 1, 2, 3, 4)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Стоит отметить, что повторяющиеся элементы не добавляются в множество,
+а также, что порядок элементов произвольный.
+
+
+### Удаление элементов из множества
+
+Элементы из множества удаляются с помощью методов `-` и `--`, результат также должен присваиваться новой переменной:
+
+{% tabs set-remove-element %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = Set(1, 2, 3, 4, 5) // HashSet(5, 1, 2, 3, 4)
+val b = a - 5 // HashSet(1, 2, 3, 4)
+val c = b -- Seq(3, 4) // HashSet(1, 2)
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+
+## Диапазон (Range)
+
+`Range` часто используется для заполнения структур данных и для `for` выражений.
+Эти REPL примеры демонстрируют, как создавать диапазоны:
+
+{% tabs range-init %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+1 to 5 // Range(1, 2, 3, 4, 5)
+1 until 5 // Range(1, 2, 3, 4)
+1 to 10 by 2 // Range(1, 3, 5, 7, 9)
+'a' to 'c' // NumericRange(a, b, c)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Range можно использовать для заполнения коллекций:
+
+{% tabs range-conversion %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val x = (1 to 5).toList // List(1, 2, 3, 4, 5)
+val x = (1 to 5).toBuffer // ArrayBuffer(1, 2, 3, 4, 5)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Они также используются в `for` выражениях:
+
+{% tabs range-iteration class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+scala> for (i <- 1 to 3) println(i)
+1
+2
+3
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+scala> for i <- 1 to 3 do println(i)
+1
+2
+3
+```
+{% endtab %}
+
+{% endtabs %}
+
+Во многих коллекциях есть метод `range`:
+
+{% tabs range-methods %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+Vector.range(1, 5) // Vector(1, 2, 3, 4)
+List.range(1, 10, 2) // List(1, 3, 5, 7, 9)
+Set.range(1, 10) // HashSet(5, 1, 6, 9, 2, 7, 3, 8, 4)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Диапазоны также полезны для создания тестовых коллекций:
+
+{% tabs range-tests %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val evens = (0 to 10 by 2).toList // List(0, 2, 4, 6, 8, 10)
+val odds = (1 to 10 by 2).toList // List(1, 3, 5, 7, 9)
+val doubles = (1 to 5).map(_ * 2.0) // Vector(2.0, 4.0, 6.0, 8.0, 10.0)
+
+// Создание Map
+val map = (1 to 3).map(e => (e,s"$e")).toMap
+// map: Map[Int, String] = Map(1 -> "1", 2 -> "2", 3 -> "3")
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+## Больше деталей
+
+Если вам нужна дополнительная информация о специализированных коллекциях, см. следующие ресурсы:
+
+- [Конкретные неизменяемые классы коллекций](https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html)
+- [Конкретные изменяемые классы коллекций](https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html)
+- [Как устроены коллекции? Какую из них следует выбрать?](https://docs.scala-lang.org/tutorials/FAQ/collections.html)
+
+
+
+[strict]: {% link _overviews/core/architecture-of-scala-213-collections.md %}
+[collections1]: /resources/images/tour/collections-diagram-213.svg
+[collections2]: /resources/images/tour/collections-immutable-diagram-213.svg
+[collections3]: /resources/images/tour/collections-mutable-diagram-213.svg
diff --git a/_ru/scala3/book/collections-intro.md b/_ru/scala3/book/collections-intro.md
new file mode 100644
index 0000000000..92793002fe
--- /dev/null
+++ b/_ru/scala3/book/collections-intro.md
@@ -0,0 +1,22 @@
+---
+layout: multipage-overview
+title: Коллекции в Scala
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: chapter
+description: На этой странице представлено введение в общие классы коллекций и их методы в Scala 3.
+language: ru
+num: 36
+previous-page: packaging-imports
+next-page: collections-classes
+---
+
+В этой главе представлены наиболее распространенные коллекции Scala 3 и сопутствующие им методы.
+Scala поставляется с множеством типов коллекций,
+Вы можете многого добиться, начав использовать лишь небольшое количество типов, а затем, по мере необходимости, начать применять остальные.
+Точно так же у каждого типа есть десятки методов,
+облегчающих разработку, но можно многого добиться, начав лишь с нескольких.
+
+Поэтому в этом разделе представлены наиболее распространенные типы и методы коллекций,
+которые вам понадобятся для начала работы.
diff --git a/_ru/scala3/book/collections-methods.md b/_ru/scala3/book/collections-methods.md
new file mode 100644
index 0000000000..503b52d705
--- /dev/null
+++ b/_ru/scala3/book/collections-methods.md
@@ -0,0 +1,662 @@
+---
+layout: multipage-overview
+title: Методы в коллекциях
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: section
+description: На этой странице показаны общие методы классов коллекций Scala 3.
+language: ru
+num: 38
+previous-page: collections-classes
+next-page: collections-summary
+---
+
+
+
+Важным преимуществом коллекций Scala является то, что они поставляются с десятками методов “из коробки”,
+которые доступны как для неизменяемых, так и для изменяемых типов коллекций.
+Больше нет необходимости писать пользовательские циклы `for` каждый раз, когда нужно работать с коллекцией.
+При переходе от одного проекта к другому, можно обнаружить, что используются одни и те же методы.
+
+В коллекциях доступны _десятки_ методов, поэтому здесь показаны не все из них.
+Показаны только некоторые из наиболее часто используемых методов, в том числе:
+
+- `map`
+- `filter`
+- `foreach`
+- `head`
+- `tail`
+- `take`, `takeWhile`
+- `drop`, `dropWhile`
+- `reduce`
+
+Следующие методы работают со всеми типами последовательностей, включая `List`, `Vector`, `ArrayBuffer` и т.д.
+Примеры рассмотрены на `List`-е, если не указано иное.
+
+> Важно напомнить, что ни один из методов в `List` не изменяет список.
+> Все они работают в функциональном стиле, то есть возвращают новую коллекцию с измененными результатами.
+
+## Примеры распространенных методов
+
+Для общего представления в примерах ниже показаны некоторые из наиболее часто используемых методов коллекций.
+Вот несколько методов, которые не используют лямбда-выражения:
+
+{% tabs common-method-examples %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10)
+
+a.distinct // List(10, 20, 30, 40)
+a.drop(2) // List(30, 40, 10)
+a.dropRight(2) // List(10, 20, 30)
+a.head // 10
+a.headOption // Some(10)
+a.init // List(10, 20, 30, 40)
+a.intersect(List(19,20,21)) // List(20)
+a.last // 10
+a.lastOption // Some(10)
+a.slice(2,4) // List(30, 40)
+a.tail // List(20, 30, 40, 10)
+a.take(3) // List(10, 20, 30)
+a.takeRight(2) // List(40, 10)
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+### Функции высшего порядка и лямбда-выражения
+
+Далее будут показаны некоторые часто используемые функции высшего порядка (HOF),
+которые принимают лямбды (анонимные функции).
+Для начала приведем несколько вариантов лямбда-синтаксиса,
+начиная с самой длинной формы, поэтапно переходящей к наиболее сжатой:
+
+{% tabs higher-order-functions-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+// все эти функции одинаковые и возвращают
+// одно и тоже: List(10, 20, 10)
+
+a.filter((i: Int) => i < 25) // 1. наиболее расширенная форма
+a.filter((i) => i < 25) // 2. `Int` необязателен
+a.filter(i => i < 25) // 3. скобки можно опустить
+a.filter(_ < 25) // 4. `i` необязателен
+```
+{% endtab %}
+
+{% endtabs %}
+
+В этих примерах:
+
+1. Первый пример показывает самую длинную форму.
+ Такое многословие требуется _редко_, только в самых сложных случаях.
+2. Компилятор знает, что `a` содержит `Int`, поэтому нет необходимости повторять это в функции.
+3. Если в функции только один параметр, например `i`, то скобки не нужны.
+4. В случае одного параметра, если он появляется в анонимной функции только раз, его можно заменить на `_`.
+
+В главе [Анонимные функции][lambdas] представлена более подробная информация
+и примеры правил, связанных с сокращением лямбда-выражений.
+
+Примеры других HOF, использующих краткий лямбда-синтаксис:
+
+{% tabs anonymous-functions-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+a.dropWhile(_ < 25) // List(30, 40, 10)
+a.filter(_ > 100) // List()
+a.filterNot(_ < 25) // List(30, 40)
+a.find(_ > 20) // Some(30)
+a.takeWhile(_ < 30) // List(10, 20)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Важно отметить, что HOF также принимают в качестве параметров методы и функции, а не только лямбда-выражения.
+Вот несколько примеров, в которых используется метод с именем `double`.
+Снова показаны несколько вариантов лямбда-выражений:
+
+{% tabs method-as-parameter-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+def double(i: Int) = i * 2
+
+// these all return `List(20, 40, 60, 80, 20)`
+a.map(i => double(i))
+a.map(double(_))
+a.map(double)
+```
+{% endtab %}
+
+{% endtabs %}
+
+В последнем примере, когда анонимная функция состоит из одного вызова функции, принимающей один аргумент,
+нет необходимости указывать имя аргумента, поэтому даже `_` не требуется.
+
+Наконец, HOF можно комбинировать:
+
+{% tabs higher-order-functions-combination-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+// выдает `List(100, 200)`
+a.filter(_ < 40)
+ .takeWhile(_ < 30)
+ .map(_ * 10)
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+## Пример данных
+
+В следующих разделах используются такие списки:
+
+{% tabs sample-data %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val oneToTen = (1 to 10).toList
+val names = List("adam", "brandy", "chris", "david")
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+## `map`
+
+Метод `map` проходит через каждый элемент в списке, применяя переданную функцию к элементу, по одному за раз;
+затем возвращается новый список с измененными элементами.
+
+Вот пример применения метода `map` к списку `oneToTen`:
+
+{% tabs map-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+scala> val doubles = oneToTen.map(_ * 2)
+doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Также можно писать анонимные функции, используя более длинную форму, например:
+
+{% tabs map-example-anonymous %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+scala> val doubles = oneToTen.map(i => i * 2)
+doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Однако в этом документе будет всегда использоваться первая, более короткая форма.
+
+Вот еще несколько примеров применения метода `map` к `oneToTen` и `names`:
+
+{% tabs few-more-examples %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+scala> val capNames = names.map(_.capitalize)
+capNames: List[String] = List(Adam, Brandy, Chris, David)
+
+scala> val nameLengthsMap = names.map(s => (s, s.length)).toMap
+nameLengthsMap: Map[String, Int] = Map(adam -> 4, brandy -> 6, chris -> 5, david -> 5)
+
+scala> val isLessThanFive = oneToTen.map(_ < 5)
+isLessThanFive: List[Boolean] = List(true, true, true, true, false, false, false, false, false, false)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Как показано в последних двух примерах, совершенно законно (и распространено) использование `map` для возврата коллекции,
+которая имеет тип, отличный от исходного типа.
+
+
+## `filter`
+
+Метод `filter` создает новый список, содержащий только те элементы, которые удовлетворяют предоставленному предикату.
+Предикат или условие — это функция, которая возвращает `Boolean` (`true` или `false`).
+Вот несколько примеров:
+
+{% tabs filter-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+scala> val lessThanFive = oneToTen.filter(_ < 5)
+lessThanFive: List[Int] = List(1, 2, 3, 4)
+
+scala> val evens = oneToTen.filter(_ % 2 == 0)
+evens: List[Int] = List(2, 4, 6, 8, 10)
+
+scala> val shortNames = names.filter(_.length <= 4)
+shortNames: List[String] = List(adam)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Отличительной особенностью функциональных методов коллекций является то,
+что их можно объединять вместе для решения задач.
+Например, в этом примере показано, как связать `filter` и `map`:
+
+{% tabs filter-example-anonymous %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+oneToTen.filter(_ < 4).map(_ * 10)
+```
+{% endtab %}
+
+{% endtabs %}
+
+REPL показывает результат:
+
+{% tabs filter-example-anonymous-repl %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+scala> oneToTen.filter(_ < 4).map(_ * 10)
+val res1: List[Int] = List(10, 20, 30)
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+## `foreach`
+
+Метод `foreach` используется для перебора всех элементов коллекции.
+Стоит обратить внимание, что `foreach` используется для побочных эффектов, таких как печать информации.
+Вот пример с `names`:
+
+{% tabs foreach-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+scala> names.foreach(println)
+adam
+brandy
+chris
+david
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+
+## `head`
+
+Метод `head` взят из Lisp и других более ранних языков функционального программирования.
+Он используется для доступа к первому элементу (головному (_head_) элементу) списка:
+
+{% tabs head-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+oneToTen.head // 1
+names.head // adam
+```
+{% endtab %}
+
+{% endtabs %}
+
+`String` можно рассматривать как последовательность символов, т.е. строка также является коллекцией,
+а значит содержит соответствующие методы.
+Вот как `head` работает со строками:
+
+{% tabs string-head-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+"foo".head // 'f'
+"bar".head // 'b'
+```
+{% endtab %}
+
+{% endtabs %}
+
+`head` — отличный метод для работы, но в качестве предостережения следует помнить, что
+он также может генерировать исключение при вызове для пустой коллекции:
+
+{% tabs head-error-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val emptyList = List[Int]() // emptyList: List[Int] = List()
+emptyList.head // java.util.NoSuchElementException: head of empty list
+```
+{% endtab %}
+
+{% endtabs %}
+
+Чтобы не натыкаться на исключение вместо `head` желательно использовать `headOption`,
+особенно при разработке в функциональном стиле:
+
+{% tabs head-option-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+emptyList.headOption // None
+```
+{% endtab %}
+
+{% endtabs %}
+
+`headOption` не генерирует исключение, а возвращает тип `Option` со значением `None`.
+Более подробно о функциональном стиле программирования будет рассказано [в соответствующей главе][fp-intro].
+
+
+## `tail`
+
+Метод `tail` также взят из Lisp и используется для вывода всех элементов в списке после `head`.
+
+{% tabs tail-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+oneToTen.head // 1
+oneToTen.tail // List(2, 3, 4, 5, 6, 7, 8, 9, 10)
+
+names.head // adam
+names.tail // List(brandy, chris, david)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Так же, как и `head`, `tail` можно использовать со строками:
+
+{% tabs string-tail-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+"foo".tail // "oo"
+"bar".tail // "ar"
+```
+{% endtab %}
+
+{% endtabs %}
+
+`tail` выбрасывает исключение _java.lang.UnsupportedOperationException_, если список пуст,
+поэтому, как и в случае с `head` и `headOption`, существует также метод `tailOption`,
+который предпочтительнее в функциональном программировании.
+
+Список матчится, поэтому можно использовать такие выражения:
+
+{% tabs tail-match-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val x :: xs = names
+```
+{% endtab %}
+
+{% endtabs %}
+
+Помещение этого кода в REPL показывает, что `x` назначается заглавному элементу списка, а `xs` назначается "хвосту":
+
+{% tabs tail-match-example-repl %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+scala> val x :: xs = names
+val x: String = adam
+val xs: List[String] = List(brandy, chris, david)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Подобное сопоставление с образцом полезно во многих случаях, например, при написании метода `sum` с использованием рекурсии:
+
+{% tabs tail-match-sum-example class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+def sum(list: List[Int]): Int = list match {
+ case Nil => 0
+ case x :: xs => x + sum(xs)
+}
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+def sum(list: List[Int]): Int = list match
+ case Nil => 0
+ case x :: xs => x + sum(xs)
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+
+## `take`, `takeRight`, `takeWhile`
+
+Методы `take`, `takeRight` и `takeWhile` предоставляют удобный способ “брать” (_taking_) элементы из списка для создания нового.
+Примеры `take` и `takeRight`:
+
+{% tabs take-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+oneToTen.take(1) // List(1)
+oneToTen.take(2) // List(1, 2)
+
+oneToTen.takeRight(1) // List(10)
+oneToTen.takeRight(2) // List(9, 10)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Обратите внимание, как эти методы работают с «пограничными» случаями,
+когда запрашивается больше элементов, чем есть в последовательности,
+или запрашивается ноль элементов:
+
+{% tabs take-edge-cases-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+oneToTen.take(Int.MaxValue) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+oneToTen.takeRight(Int.MaxValue) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+oneToTen.take(0) // List()
+oneToTen.takeRight(0) // List()
+```
+{% endtab %}
+
+{% endtabs %}
+
+А это `takeWhile`, который работает с функцией-предикатом:
+
+{% tabs take-while-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+oneToTen.takeWhile(_ < 5) // List(1, 2, 3, 4)
+names.takeWhile(_.length < 5) // List(adam)
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+## `drop`, `dropRight`, `dropWhile`
+
+`drop`, `dropRight` и `dropWhile` удаляют элементы из списка
+и, по сути, противоположны своим аналогам “take”.
+Вот некоторые примеры:
+
+{% tabs drop-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+oneToTen.drop(1) // List(2, 3, 4, 5, 6, 7, 8, 9, 10)
+oneToTen.drop(5) // List(6, 7, 8, 9, 10)
+
+oneToTen.dropRight(8) // List(1, 2)
+oneToTen.dropRight(7) // List(1, 2, 3)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Пограничные случаи:
+
+{% tabs drop-edge-cases-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+oneToTen.drop(Int.MaxValue) // List()
+oneToTen.dropRight(Int.MaxValue) // List()
+oneToTen.drop(0) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+oneToTen.dropRight(0) // List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+```
+{% endtab %}
+
+{% endtabs %}
+
+А это `dropWhile`, который работает с функцией-предикатом:
+
+{% tabs drop-while-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+oneToTen.dropWhile(_ < 5) // List(5, 6, 7, 8, 9, 10)
+names.dropWhile(_ != "chris") // List(chris, david)
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+## `reduce`
+
+Метод `reduce` позволяет свертывать коллекцию до одного агрегируемого значения.
+Он принимает функцию (или анонимную функцию) и последовательно применяет эту функцию к элементам в списке.
+
+Лучший способ объяснить `reduce` — создать небольшой вспомогательный метод.
+Например, метод `add`, который складывает вместе два целых числа,
+а также предоставляет хороший вывод отладочной информации:
+
+{% tabs reduce-example class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+def add(x: Int, y: Int): Int = {
+ val theSum = x + y
+ println(s"received $x and $y, their sum is $theSum")
+ theSum
+}
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+def add(x: Int, y: Int): Int =
+ val theSum = x + y
+ println(s"received $x and $y, their sum is $theSum")
+ theSum
+```
+{% endtab %}
+
+{% endtabs %}
+
+Рассмотрим список:
+
+{% tabs reduce-example-init %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = List(1,2,3,4)
+```
+{% endtab %}
+
+{% endtabs %}
+
+вот что происходит, когда в `reduce` передается метод `add`:
+
+{% tabs reduce-example-evaluation %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+scala> a.reduce(add)
+received 1 and 2, their sum is 3
+received 3 and 3, their sum is 6
+received 6 and 4, their sum is 10
+res0: Int = 10
+```
+{% endtab %}
+
+{% endtabs %}
+
+Как видно из результата, функция `reduce` использует `add` для сокращения списка `a` до единственного значения,
+в данном случае — суммы всех чисел в списке.
+
+`reduce` можно использовать с анонимными функциями:
+
+{% tabs reduce-example-sum %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+scala> a.reduce(_ + _)
+res0: Int = 10
+```
+{% endtab %}
+
+{% endtabs %}
+
+Аналогично можно использовать другие функции, например, умножение:
+
+{% tabs reduce-example-multiply %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+scala> a.reduce(_ * _)
+res1: Int = 24
+```
+{% endtab %}
+
+{% endtabs %}
+
+> Важная концепция, которую следует знать о `reduce`, заключается в том, что, как следует из ее названия
+> (_reduce_ - сокращать), она используется для сокращения коллекции до одного значения.
+
+
+## Дальнейшее изучение коллекций
+
+В коллекциях Scala есть десятки дополнительных методов, которые избавляют от необходимости писать еще один цикл `for`.
+Более подробную информацию о коллекциях Scala см.
+в разделе [Изменяемые и неизменяемые коллекции][mut-immut-colls]
+и [Архитектура коллекций Scala][architecture].
+
+> В качестве последнего примечания, при использовании Java-кода в проекте Scala,
+> коллекции Java можно преобразовать в коллекции Scala.
+> После этого, их можно использовать в выражениях `for`,
+> а также воспользоваться преимуществами методов функциональных коллекций Scala.
+> Более подробную информацию можно найти в разделе [Взаимодействие с Java][interacting].
+
+
+[interacting]: {% link _overviews/scala3-book/interacting-with-java.md %}
+[lambdas]: {% link _overviews/scala3-book/fun-anonymous-functions.md %}
+[fp-intro]: {% link _overviews/scala3-book/fp-intro.md %}
+[mut-immut-colls]: {% link _overviews/collections-2.13/overview.md %}
+[architecture]: {% link _overviews/core/architecture-of-scala-213-collections.md %}
+
diff --git a/_ru/scala3/book/collections-summary.md b/_ru/scala3/book/collections-summary.md
new file mode 100644
index 0000000000..6e852ad359
--- /dev/null
+++ b/_ru/scala3/book/collections-summary.md
@@ -0,0 +1,35 @@
+---
+layout: multipage-overview
+title: Обзор
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: section
+description: На этой странице представлен краткий итог главы «Коллекции».
+language: ru
+num: 39
+previous-page: collections-methods
+next-page: fp-intro
+---
+
+В этой главе представлен обзор общих коллекций Scala 3 и сопутствующих им методов.
+Как было показано, Scala поставляется с множеством коллекций и методов.
+
+Если вам нужно увидеть более подробную информацию о типах коллекций,
+показанных в этой главе, см. их Scaladoc страницы:
+
+- [List](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html)
+- [Vector](https://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html)
+- [ArrayBuffer](https://www.scala-lang.org/api/current/scala/collection/mutable/ArrayBuffer.html)
+- [Range](https://www.scala-lang.org/api/current/scala/collection/immutable/Range.html)
+
+Также упоминавшиеся неизменяемые `Map` и `Set`:
+
+- [Map](https://www.scala-lang.org/api/current/scala/collection/immutable/Map.html)
+- [Set](https://www.scala-lang.org/api/current/scala/collection/immutable/Set.html)
+
+и изменяемые `Map` и `Set`:
+
+- [Map](https://www.scala-lang.org/api/current/scala/collection/mutable/Map.html)
+- [Set](https://www.scala-lang.org/api/current/scala/collection/mutable/Set.html)
+
diff --git a/_ru/scala3/book/control-structures.md b/_ru/scala3/book/control-structures.md
index b72b476278..37e78c1633 100644
--- a/_ru/scala3/book/control-structures.md
+++ b/_ru/scala3/book/control-structures.md
@@ -450,14 +450,14 @@ AR: Arizona
{% endtab %}
{% endtabs %}
-Когда цикл `for` перебирает карту, каждая пара ключ/значение привязывается
+Когда цикл `for` перебирает мапу, каждая пара ключ/значение привязывается
к переменным `abbrev` и `fullName`, которые находятся в кортеже:
```scala
(abbrev, fullName) <- states
```
-По мере выполнения цикла переменная `abbrev` принимает значение текущего _ключа_ в карте,
+По мере выполнения цикла переменная `abbrev` принимает значение текущего _ключа_ в мапе,
а переменная `fullName` - соответствующему ключу _значению_.
## Выражение `for`
diff --git a/_ru/scala3/book/fp-functional-error-handling.md b/_ru/scala3/book/fp-functional-error-handling.md
new file mode 100644
index 0000000000..bf6d299fbd
--- /dev/null
+++ b/_ru/scala3/book/fp-functional-error-handling.md
@@ -0,0 +1,436 @@
+---
+layout: multipage-overview
+title: Функциональная обработка ошибок
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: section
+description: В этом разделе представлено введение в функциональную обработку ошибок в Scala 3.
+language: ru
+num: 45
+previous-page: fp-functions-are-values
+next-page: fp-summary
+---
+
+
+
+Функциональное программирование похоже на написание ряда алгебраических уравнений,
+и поскольку алгебра не имеет null значений или исключений, они не используются и в ФП.
+Что поднимает интересный вопрос: как быть в ситуациях, в которых вы обычно используете null значение или исключение программируя в ООП стиле?
+
+Решение Scala заключается в использовании конструкций, основанных на классах типа `Option`/`Some`/`None`.
+Этот урок представляет собой введение в использование такого подхода.
+
+Примечание:
+
+- классы `Some` и `None` являются подклассами `Option`
+- вместо того чтобы многократно повторять “`Option`/`Some`/`None`”,
+ следующий текст обычно просто ссылается на “`Option`” или на “классы `Option`”
+
+
+## Первый пример
+
+Хотя этот первый пример не имеет дело с `null` значениями, это хороший способ познакомиться с классами `Option`.
+
+Представим, что нужно написать метод, который упрощает преобразование строк в целочисленные значения.
+И нужен элегантный способ обработки исключения, которое возникает,
+когда метод получает строку типа `"Hello"` вместо `"1"`.
+Первое предположение о таком методе может выглядеть следующим образом:
+
+{% tabs fp-java-try class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+def makeInt(s: String): Int =
+ try {
+ Integer.parseInt(s.trim)
+ } catch {
+ case e: Exception => 0
+ }
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+def makeInt(s: String): Int =
+ try
+ Integer.parseInt(s.trim)
+ catch
+ case e: Exception => 0
+```
+{% endtab %}
+
+{% endtabs %}
+
+Если преобразование работает, метод возвращает правильное значение `Int`, но в случае сбоя метод возвращает `0`.
+Для некоторых целей это может быть хорошо, но не совсем точно.
+Например, метод мог получить `"0"`, но мог также получить `"foo"`, `"bar"`
+или бесконечное количество других строк, которые выдадут исключение.
+Это реальная проблема: как определить, когда метод действительно получил `"0"`, а когда получил что-то еще?
+При таком подходе нет способа узнать правильный ответ наверняка.
+
+
+## Использование Option/Some/None
+
+Распространенным решением этой проблемы в Scala является использование классов,
+известных как `Option`, `Some` и `None`.
+Классы `Some` и `None` являются подклассами `Option`, поэтому решение работает следующим образом:
+
+- объявляется, что `makeInt` возвращает тип `Option`
+- если `makeInt` получает строку, которую он _может_ преобразовать в `Int`, ответ помещается внутрь `Some`
+- если `makeInt` получает строку, которую _не может_ преобразовать, то возвращает `None`
+
+Вот доработанная версия `makeInt`:
+
+{% tabs fp--try-option class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+def makeInt(s: String): Option[Int] =
+ try {
+ Some(Integer.parseInt(s.trim))
+ } catch {
+ case e: Exception => None
+ }
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+def makeInt(s: String): Option[Int] =
+ try
+ Some(Integer.parseInt(s.trim))
+ catch
+ case e: Exception => None
+```
+{% endtab %}
+
+{% endtabs %}
+
+Этот код можно прочитать следующим образом:
+“Когда данная строка преобразуется в целое число, верните значение `Int`, заключенное в `Some`, например `Some(1)`.
+Когда строка не может быть преобразована в целое число и генерируется исключение, метод возвращает значение `None`.”
+
+Эти примеры показывают, как работает `makeInt`:
+
+{% tabs fp-try-option-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = makeInt("1") // Some(1)
+val b = makeInt("one") // None
+```
+{% endtab %}
+
+{% endtabs %}
+
+Как показано, строка `"1"` приводится к `Some(1)`, а строка `"one"` - к `None`.
+В этом суть альтернативного подхода к обработке ошибок.
+Данная техника используется для того, чтобы методы могли возвращать _значения_ вместо _исключений_.
+В других ситуациях значения `Option` также используются для замены `null` значений.
+
+Примечание:
+
+- этот подход используется во всех классах библиотеки Scala, а также в сторонних библиотеках Scala.
+- ключевым моментом примера является то, что функциональные методы не генерируют исключения;
+ вместо этого они возвращают такие значения, как `Option`.
+
+
+## Потребитель makeInt
+
+Теперь представим, что мы являемся потребителем метода `makeInt`.
+Известно, что он возвращает подкласс `Option[Int]`, поэтому возникает вопрос:
+как работать с такими возвращаемыми типами?
+
+Есть два распространенных ответа, в зависимости от потребностей:
+
+- использование `match` выражений
+- использование `for` выражений
+
+## Использование `match` выражений
+
+Одним из возможных решений является использование выражения `match`:
+
+{% tabs fp-option-match class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+makeInt(x) match {
+ case Some(i) => println(i)
+ case None => println("That didn’t work.")
+}
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+makeInt(x) match
+ case Some(i) => println(i)
+ case None => println("That didn’t work.")
+```
+{% endtab %}
+
+{% endtabs %}
+
+В этом примере, если `x` можно преобразовать в `Int`, вычисляется первый вариант в правой части предложения `case`;
+если `x` не может быть преобразован в `Int`, вычисляется второй вариант в правой части предложения `case`.
+
+
+## Использование `for` выражений
+
+Другим распространенным решением является использование выражения `for`, то есть комбинации `for`/`yield`.
+Например, представим, что необходимо преобразовать три строки в целочисленные значения, а затем сложить их.
+Решение задачи с использованием выражения `for`:
+
+{% tabs fp-for-comprehension class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+val y = for {
+ a <- makeInt(stringA)
+ b <- makeInt(stringB)
+ c <- makeInt(stringC)
+} yield {
+ a + b + c
+}
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+val y = for
+ a <- makeInt(stringA)
+ b <- makeInt(stringB)
+ c <- makeInt(stringC)
+yield
+ a + b + c
+```
+{% endtab %}
+
+{% endtabs %}
+
+После выполнения этого выражения `y` может принять одно из двух значений:
+
+- если _все_ три строки конвертируются в значения `Int`, `y` будет равно `Some[Int]`, т.е. целым числом, обернутым внутри `Some`
+- если _какая-либо_ из трех строк не может быть преобразована в `Int`, `y` равен `None`
+
+Это можно проверить на примере:
+
+{% tabs fp-for-comprehension-evaluation class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+val stringA = "1"
+val stringB = "2"
+val stringC = "3"
+
+val y = for {
+ a <- makeInt(stringA)
+ b <- makeInt(stringB)
+ c <- makeInt(stringC)
+} yield {
+ a + b + c
+}
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+val stringA = "1"
+val stringB = "2"
+val stringC = "3"
+
+val y = for
+ a <- makeInt(stringA)
+ b <- makeInt(stringB)
+ c <- makeInt(stringC)
+yield
+ a + b + c
+```
+{% endtab %}
+
+{% endtabs %}
+
+С этими демонстрационными данными переменная `y` примет значение `Some(6)`.
+
+Чтобы увидеть негативный кейс, достаточно изменить любую из строк на что-то, что нельзя преобразовать в целое число.
+В этом случае `y` равно `None`:
+
+{% tabs fp-for-comprehension-failure-result %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+y: Option[Int] = None
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+## Восприятие Option, как контейнера
+
+Для лучшего восприятия `Option`, его можно представить как _контейнер_:
+
+- `Some` представляет собой контейнер с одним элементом
+- `None` не является контейнером, в нем ничего нет
+
+Если предпочтительнее думать об `Option` как о ящике, то `None` подобен пустому ящику.
+Что-то в нём могло быть, но нет.
+
+
+## Использование `Option` для замены `null`
+
+Возвращаясь к значениям `null`, место, где `null` значение может незаметно проникнуть в код, — класс, подобный этому:
+
+{% tabs fp=case-class-nulls %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+class Address(
+ var street1: String,
+ var street2: String,
+ var city: String,
+ var state: String,
+ var zip: String
+)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Хотя каждый адрес имеет значение `street1`, значение `street2` не является обязательным.
+В результате полю `street2` можно присвоить значение `null`:
+
+{% tabs fp-case-class-nulls-example class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+val santa = new Address(
+ "1 Main Street",
+ null, // <-- О! Значение null!
+ "North Pole",
+ "Alaska",
+ "99705"
+)
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+val santa = Address(
+ "1 Main Street",
+ null, // <-- О! Значение null!
+ "North Pole",
+ "Alaska",
+ "99705"
+)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Исторически сложилось так, что в этой ситуации разработчики использовали пустые строки и значения `null`,
+оба варианта это “костыль” для решения основной проблемы: `street2` - _необязательное_ поле.
+В Scala и других современных языках правильное решение состоит в том,
+чтобы заранее объявить, что `street2` является необязательным:
+
+
+{% tabs fp-case-class-with-options %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+class Address(
+ var street1: String,
+ var street2: Option[String], // необязательное значение
+ var city: String,
+ var state: String,
+ var zip: String
+)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Теперь можно написать более точный код:
+
+{% tabs fp-case-class-with-options-example-none class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+val santa = new Address(
+ "1 Main Street",
+ None, // 'street2' не имеет значения
+ "North Pole",
+ "Alaska",
+ "99705"
+)
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+val santa = Address(
+ "1 Main Street",
+ None, // 'street2' не имеет значения
+ "North Pole",
+ "Alaska",
+ "99705"
+)
+```
+{% endtab %}
+
+{% endtabs %}
+
+или так:
+
+{% tabs fp-case-class-with-options-example-some class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+val santa = new Address(
+ "123 Main Street",
+ Some("Apt. 2B"),
+ "Talkeetna",
+ "Alaska",
+ "99676"
+)
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+val santa = Address(
+ "123 Main Street",
+ Some("Apt. 2B"),
+ "Talkeetna",
+ "Alaska",
+ "99676"
+)
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+
+## `Option` — не единственное решение
+
+В этом разделе основное внимание уделялось `Option` классам, но у Scala есть несколько других альтернатив.
+
+Например, три класса, известные как `Try`/`Success`/`Failure`, работают также,
+но (а) эти классы в основном используются, когда код может генерировать исключения,
+и (б) когда желательно использовать класс `Failure`, потому что он дает доступ к сообщению об исключении.
+Например, классы `Try` обычно используются при написании методов, которые взаимодействуют с файлами,
+базами данных или интернет-службами, поскольку эти функции могут легко создавать исключения.
+
+
+## Краткое ревью
+
+Этот раздел был довольно большим, поэтому давайте подведем краткое ревью:
+
+- функциональные программисты не используют `null` значения
+- основной заменой `null` значениям является использование классов `Option`
+- функциональные методы не выдают исключений; вместо этого они возвращают такие значения, как `Option`, `Try` или `Either`
+- распространенными способами работы со значениями `Option` являются выражения `match` и `for`
+- `Option` можно рассматривать как контейнеры с одним элементом (`Some`) и без элементов (`None`)
+- `Option` также можно использовать для необязательных параметров конструктора или метода
diff --git a/_ru/scala3/book/fp-functions-are-values.md b/_ru/scala3/book/fp-functions-are-values.md
new file mode 100644
index 0000000000..62cacc4540
--- /dev/null
+++ b/_ru/scala3/book/fp-functions-are-values.md
@@ -0,0 +1,161 @@
+---
+layout: multipage-overview
+title: Функции — это значения
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: section
+description: В этом разделе рассматривается использование функций в качестве значений в функциональном программировании.
+language: ru
+num: 44
+previous-page: fp-pure-functions
+next-page: fp-functional-error-handling
+---
+
+
+Хотя каждый когда-либо созданный язык программирования, вероятно, позволяет писать чистые функции,
+вторая важная особенность ФП на Scala заключается в том, что _функции можно создавать как значения_,
+точно так же, как создаются значения `String` и `Int`.
+
+Эта особенность даёт много преимуществ, опишем наиболее распространенные из них:
+(a) можно определять методы, принимающие в качестве параметров функции
+и (b) можно передавать функции в качестве параметров в методы.
+
+Такой подход можно было наблюдать в предыдущих главах, когда демонстрировались такие методы, как `map` и `filter`:
+
+{% tabs fp-function-as-values-anonymous %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val nums = (1 to 10).toList
+
+val doubles = nums.map(_ * 2) // удваивает каждое значение
+val lessThanFive = nums.filter(_ < 5) // List(1,2,3,4)
+```
+{% endtab %}
+
+{% endtabs %}
+
+В этих примерах анонимные функции передаются в `map` и `filter`.
+
+> Анонимные функции также известны как _лямбды_ (_lambdas_).
+
+Помимо передачи анонимных функций в `filter` и `map`, в них также можно передать _методы_:
+
+{% tabs fp-function-as-values-defined %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+// два метода
+def double(i: Int): Int = i * 2
+def underFive(i: Int): Boolean = i < 5
+
+// передача этих методов в filter и map
+val doubles = nums.filter(underFive).map(double)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Возможность обращаться с методами и функциями как со значениями — мощное свойство,
+предоставляемое языками функционального программирования.
+
+> Технически функция, которая принимает другую функцию в качестве входного параметра, известна как _функция высшего порядка_.
+> (Если вам нравится юмор, как кто-то однажды написал, это все равно, что сказать,
+> что класс, который принимает экземпляр другого класса в качестве параметра конструктора,
+> является классом высшего порядка.)
+
+
+## Функции, анонимные функции и методы
+
+В примерах выше анонимная функция это:
+
+{% tabs fp-anonymous-function-short %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+_ * 2
+```
+{% endtab %}
+
+{% endtabs %}
+
+Как было показано в обсуждении [функций высшего порядка][hofs], `_ * 2` - сокращенная версия синтаксиса:
+
+{% tabs fp-anonymous-function-full %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+(i: Int) => i * 2
+```
+{% endtab %}
+
+{% endtabs %}
+
+Такие функции называются “анонимными”, потому что им не присваивается определенное имя.
+Для того чтобы это имя задать, достаточно просто присвоить его переменной:
+
+{% tabs fp-function-assignement %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val double = (i: Int) => i * 2
+```
+{% endtab %}
+
+{% endtabs %}
+
+Теперь появилась именованная функция, назначенная переменной `double`.
+Можно использовать эту функцию так же, как используется метод:
+
+{% tabs fp-function-used-like-method %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+double(2) // 4
+```
+{% endtab %}
+
+{% endtabs %}
+
+В большинстве случаев не имеет значения, является ли `double` функцией или методом;
+Scala позволяет обращаться с ними одинаково.
+За кулисами технология Scala, которая позволяет обращаться с методами так же,
+как с функциями, известна как [Eta Expansion][eta].
+
+Эта способность беспрепятственно передавать функции в качестве переменных
+является отличительной чертой функциональных языков программирования, таких как Scala.
+И, как было видно на примерах `map` и `filter`,
+возможность передавать функции в другие функции помогает создавать код,
+который является кратким и при этом читабельным — _выразительным_.
+
+Вот еще несколько примеров:
+
+{% tabs fp-function-as-values-example %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+List("bob", "joe").map(_.toUpperCase) // List(BOB, JOE)
+List("bob", "joe").map(_.capitalize) // List(Bob, Joe)
+List("plum", "banana").map(_.length) // List(4, 6)
+
+val fruits = List("apple", "pear")
+fruits.map(_.toUpperCase) // List(APPLE, PEAR)
+fruits.flatMap(_.toUpperCase) // List(A, P, P, L, E, P, E, A, R)
+
+val nums = List(5, 1, 3, 11, 7)
+nums.map(_ * 2) // List(10, 2, 6, 22, 14)
+nums.filter(_ > 3) // List(5, 11, 7)
+nums.takeWhile(_ < 6) // List(5, 1, 3)
+nums.sortWith(_ < _) // List(1, 3, 5, 7, 11)
+nums.sortWith(_ > _) // List(11, 7, 5, 3, 1)
+
+nums.takeWhile(_ < 6).sortWith(_ < _) // List(1, 3, 5)
+```
+{% endtab %}
+
+{% endtabs %}
+
+
+[hofs]: {% link _overviews/scala3-book/fun-hofs.md %}
+[eta]: {% link _overviews/scala3-book/fun-eta-expansion.md %}
diff --git a/_ru/scala3/book/fp-immutable-values.md b/_ru/scala3/book/fp-immutable-values.md
new file mode 100644
index 0000000000..24ffaf8613
--- /dev/null
+++ b/_ru/scala3/book/fp-immutable-values.md
@@ -0,0 +1,109 @@
+---
+layout: multipage-overview
+title: Неизменяемые значения
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: section
+description: В этом разделе рассматривается использование неизменяемых значений в функциональном программировании.
+language: ru
+num: 42
+previous-page: fp-what-is-fp
+next-page: fp-pure-functions
+---
+
+В чистом функциональном программировании используются только неизменяемые значения.
+В Scala это означает:
+
+- все переменные создаются как поля `val`
+- используются только неизменяемые классы коллекций, такие как `List`, `Vector` и неизменяемые классы `Map` и `Set`
+
+Использование только неизменяемых переменных поднимает интересный вопрос: если все статично, как вообще что-то меняется?
+
+Когда дело доходит до использования коллекций, один из ответов заключается в том,
+что существующая коллекция не меняется; вместо этого функция применяется к коллекции, чтобы создать новую.
+Именно здесь вступают в действие функции высшего порядка, такие как `map` и `filter`.
+
+Например, представим, что есть список имен в нижнем регистре — `List[String]`,
+и необходимо найти все имена, начинающиеся с буквы `"j"`, чтобы затем сделать первые буквы заглавными.
+В ФП код будет выглядеть так:
+
+{% tabs fp-list %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val a = List("jane", "jon", "mary", "joe")
+val b = a.filter(_.startsWith("j"))
+ .map(_.capitalize)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Как показано, исходный список `a` не меняется.
+Вместо этого к `a` применяется функция фильтрации и преобразования, чтобы создать новую коллекцию,
+и результат присваивается неизменяемой переменной `b`.
+
+Точно так же в ФП не используются классы с изменяемыми параметрами конструктора `var`.
+В ФП создание такого класса не привествуется:
+
+{% tabs fp--class-variables %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+// не стоит этого делать в ФП
+class Person(var firstName: String, var lastName: String)
+ --- ---
+```
+{% endtab %}
+
+{% endtabs %}
+
+Вместо этого обычно создаются `case` классы, чьи параметры конструктора по умолчанию неизменяемые (`val`):
+
+{% tabs fp-immutable-case-class %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+case class Person(firstName: String, lastName: String)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Теперь можно создать экземпляр `Person` как поле `val`:
+
+{% tabs fp-case-class-creation %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val reginald = Person("Reginald", "Dwight")
+```
+{% endtab %}
+
+{% endtabs %}
+
+Затем, при необходимости внести изменения в данные, используется метод `copy`,
+который поставляется с `case` классом, чтобы “обновлять данные через создание копии”,
+например так:
+
+{% tabs fp-case-class-copy %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+val elton = reginald.copy(
+ firstName = "Elton", // обновить имя
+ lastName = "John" // обновить фамилию
+)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Существуют множество других приёмов работы с неизменяемыми коллекциями и переменными.
+
+> В зависимости от задач вместо `case` классов можно создавать перечисления, trait-ы или классы.
+> Для более подробной информации см. главу [“Моделирование данных”][modeling].
+
+
+[modeling]: {% link _overviews/scala3-book/domain-modeling-intro.md %}
diff --git a/_ru/scala3/book/fp-intro.md b/_ru/scala3/book/fp-intro.md
new file mode 100644
index 0000000000..7961b13a83
--- /dev/null
+++ b/_ru/scala3/book/fp-intro.md
@@ -0,0 +1,31 @@
+---
+layout: multipage-overview
+title: Функциональное программирование
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: chapter
+description: В этой главе представлено введение в функциональное программирование в Scala 3.
+language: ru
+num: 40
+previous-page: collections-summary
+next-page: fp-what-is-fp
+---
+
+
+Scala позволяет писать код в стиле объектно-ориентированного программирования (ООП),
+в стиле функционального программирования (ФП), а также в гибридном стиле, используя оба подхода в комбинации.
+По словам [Martin Odersky](https://twitter.com/alexelcu/status/996408359514525696),
+сущность Scala — это слияние функционального и объектно-ориентированного программирования в типизированной среде:
+
+- Функции для логики
+- Объекты для модульности
+
+В этой главе предполагается, что вы знакомы с ООП и менее знакомы с ФП,
+поэтому в ней представлено краткое введение в несколько основных концепций функционального программирования:
+
+- Что такое функциональное программирование?
+- Неизменяемые значения
+- Чистые функции
+- Функции — это значения
+- Функциональная обработка ошибок
diff --git a/_ru/scala3/book/fp-pure-functions.md b/_ru/scala3/book/fp-pure-functions.md
new file mode 100644
index 0000000000..fd70cc5694
--- /dev/null
+++ b/_ru/scala3/book/fp-pure-functions.md
@@ -0,0 +1,153 @@
+---
+layout: multipage-overview
+title: Чистые функции
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: section
+description: В этом разделе рассматривается использование чистых функций в функциональном программировании.
+language: ru
+num: 43
+previous-page: fp-immutable-values
+next-page: fp-functions-are-values
+---
+
+
+Еще одна концепция, которую Scala предлагает для помощи в написании функционального кода, — это возможность писать чистые функции.
+_Чистая функция_ (_pure function_) может быть определена следующим образом:
+
+- функция `f` является чистой, если при одних и тех же входных данных `x` она всегда возвращает один и тот же результат `f(x)`
+- результат функции зависит _только_ от входных данных и её реализации
+- чистые функции только вычисляют результат, ничего не меняя за пределами этих функций
+
+Из этого следует:
+
+- чистая функция не изменяет свои входные параметры
+- она не мутирует какое-либо скрытое состояние
+- у неё нет “черных ходов”: он не читает данные из внешнего мира (включая консоль, веб-сервисы, базы данных, файлы и т.д.)
+ и не записывает данные вовне
+
+В результате этого определения каждый раз, когда вызывается чистая функция с одним и тем же входным значением (значениями),
+всегда будет выдаваться один и тот же результат.
+Например, можно вызывать функцию `double` бесконечное число раз с входным значением `2`, и всегда получать результат `4`.
+
+
+## Примеры чистых функций
+
+Учитывая это определение, методы в пакете `scala.math._` являются чистыми функциями:
+
+- `abs`
+- `ceil`
+- `max`
+
+Эти методы `String` также являются чистыми функциями:
+
+- `isEmpty`
+- `length`
+- `substring`
+
+Большинство методов в классах коллекций Scala также работают как чистые функции,
+включая `drop`, `filter`, `map` и многие другие.
+
+> В Scala _функции_ и _методы_ почти полностью взаимозаменяемы,
+> поэтому, хотя здесь используется общепринятый отраслевой термин “чистая функция”,
+> этот термин можно использовать как для описания функций, так и методов.
+> Как методы могут использоваться подобно функциям описано в главе [Eta расширение][eta].
+
+
+## Примеры “грязных” функций
+
+И наоборот, следующие функции “_грязные_” (_impure_), потому что они нарушают определение pure function:
+
+- `println` — методы, взаимодействующие с консолью, файлами, базами данных, веб-сервисами и т.д., “грязные”
+- `currentTimeMillis` — все методы, связанные с датой и временем, “грязные”,
+ потому что их вывод зависит от чего-то другого, кроме входных параметров
+- `sys.error` — методы генерации исключений “грязные”, потому что они не “просто возвращают результат”
+
+“Грязные” функции часто делают одно из следующего:
+
+- читают из скрытого состояния, т.е. обращаются к параметрам и данным,
+ не переданным в функцию явным образом в качестве входных параметров
+- запись в скрытое состояние
+- изменяют заданные им параметры или изменяют скрытые переменные, например, поля в содержащем их классе
+- выполняют какой-либо ввод-вывод с внешним миром
+
+> В общем, следует остерегаться функций с возвращаемым типом `Unit`.
+> Поскольку эти функции ничего не возвращают, логически единственная причина, по которой они когда-либо вызываются, -
+> это достижение какого-то побочного эффекта.
+> Как следствие, часто использование этих функций является “грязным”.
+
+
+## Но грязные функции все же необходимы ...
+
+Конечно, приложение не очень полезно, если оно не может читать или писать во внешний мир, поэтому рекомендуется следующее:
+
+> Напишите ядро вашего приложения, используя только “чистые” функции,
+> а затем напишите “грязную” “оболочку” вокруг этого ядра для взаимодействия с внешним миром.
+> Как кто-то однажды сказал, это все равно, что положить слой нечистой глазури на чистый торт.
+
+Важно отметить, что есть способы сделать “нечистое” взаимодействие с внешним миром более “чистым”.
+Например, можно услышать об использовании `IO` монады для обработки ввода-вывода.
+Эти темы выходят за рамки данного документа, поэтому для простоты можно думать,
+что ФП приложения имеют ядро из “чистых” функций,
+которые объединены с другими функциями для взаимодействия с внешним миром.
+
+
+## Написание “чистых” функций
+
+**Примечание**: в этом разделе для обозначения методов Scala часто используется общепринятый в отрасли термин “чистая функция”.
+
+Для написания чистых функций на Scala, достаточно писать их,
+используя синтаксис методов Scala (хотя также можно использовать и синтаксис функций Scala).
+Например, вот чистая функция, которая удваивает заданное ей входное значение:
+
+{% tabs fp-pure-function %}
+
+{% tab 'Scala 2 и 3' %}
+```scala
+def double(i: Int): Int = i * 2
+```
+{% endtab %}
+
+{% endtabs %}
+
+Вот чистая функция, которая вычисляет сумму списка целых чисел с использованием рекурсии:
+
+{% tabs fp-pure-recursive-function class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+```scala
+def sum(xs: List[Int]): Int = xs match {
+ case Nil => 0
+ case head :: tail => head + sum(tail)
+}
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+def sum(xs: List[Int]): Int = xs match
+ case Nil => 0
+ case head :: tail => head + sum(tail)
+```
+{% endtab %}
+
+{% endtabs %}
+
+Вышеописанные функции соответствуют определению “чистых”.
+
+
+## Ключевые моменты
+
+Первым ключевым моментом этого раздела является определение чистой функции:
+
+> _Чистая функция_ — это функция, которая зависит только от своих объявленных входных данных
+> и своей реализации для получения результата.
+> Она только вычисляет свой результат, не завися от внешнего мира и не изменяя его.
+
+Второй ключевой момент заключается в том, что каждое реальное приложение взаимодействует с внешним миром.
+Таким образом, упрощенный способ представления о функциональных программах состоит в том,
+что они состоят из ядра чистых функций, которые обернуты другими функциями, взаимодействующими с внешним миром.
+
+
+[eta]: {% link _overviews/scala3-book/fun-eta-expansion.md %}
diff --git a/_ru/scala3/book/fp-summary.md b/_ru/scala3/book/fp-summary.md
new file mode 100644
index 0000000000..3a3cc5498d
--- /dev/null
+++ b/_ru/scala3/book/fp-summary.md
@@ -0,0 +1,31 @@
+---
+layout: multipage-overview
+title: Обзор
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: section
+description: Этот раздел суммирует предыдущие разделы функционального программирования.
+language: ru
+num: 46
+previous-page: fp-functional-error-handling
+next-page: types-introduction
+---
+
+
+В этой главе представлено общее введение в функциональное программирование на Scala.
+Охвачены следующие темы:
+
+- Что такое функциональное программирование?
+- Неизменяемые значения
+- Чистые функции
+- Функции — это значения
+- Функциональная обработка ошибок
+
+Как уже упоминалось, функциональное программирование — обширная тема,
+поэтому все, что мы можем сделать в этой книге, — это коснуться перечисленных вводных понятий.
+Дополнительные сведения см. [в справочной документации][reference].
+
+
+[reference]: {{ site.scala3ref }}/overview.html
+
diff --git a/_ru/scala3/book/fp-what-is-fp.md b/_ru/scala3/book/fp-what-is-fp.md
new file mode 100644
index 0000000000..9e3f046542
--- /dev/null
+++ b/_ru/scala3/book/fp-what-is-fp.md
@@ -0,0 +1,48 @@
+---
+layout: multipage-overview
+title: Что такое функциональное программирование?
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: section
+description: Этот раздел дает ответ на вопрос, что такое функциональное программирование?
+language: ru
+num: 41
+previous-page: fp-intro
+next-page: fp-immutable-values
+---
+
+
+[Wikipedia](https://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
+определяет _функциональное программирование_ следующим образом:
+
+
+
+Функциональное программирование — парадигма программирования,
+в которой процесс вычисления трактуется как вычисление значений функций в математическом понимании последних.
+Функциональное программирование предполагает обходиться вычислением результатов функций от исходных данных
+и результатов других функций, и не предполагает явного хранения состояния программы.
+Соответственно, не предполагает оно и изменяемость этого состояния.
+
+
+
+В функциональном программировании функции рассматриваются как “граждане первого класса”,
+что означает, что они могут быть привязаны к именам (включая локальные идентификаторы),
+передаваться в качестве аргументов и возвращаться из других функций, как и любой другой тип данных.
+Это позволяет писать программы в декларативном и составном стиле, где небольшие функции объединяются модульным образом.
+
+
+
+Также полезно знать, что опытные функциональные программисты рассматривают свой код математически,
+что объединение чистых функций вместе похоже на объединение ряда алгебраических уравнений.
+
+Когда пишется функциональный код, вы чувствуете себя математиком, и как только понимаете парадигму,
+то хотите писать только чистые функции, которые всегда возвращают _значения_, а не исключения или null,
+чтобы можно было комбинировать чистые функции вместе.
+Ощущение, что вы пишете математические уравнения (выражения), является движущим желанием,
+заставляющим использовать _только_ чистые функции и неизменяемые значения -
+это то, что используется в алгебре и других формах математики.
+
+Функциональное программирование - это большая тема, и нет простого способа сжать её всю в одну главу.
+В следующих разделах будет представлен обзор основных тем и показаны некоторые инструменты,
+предоставляемые Scala для написания функционального кода.
diff --git a/_ru/scala3/book/fun-summary.md b/_ru/scala3/book/fun-summary.md
index 497e4d4c5e..77a72a05b2 100644
--- a/_ru/scala3/book/fun-summary.md
+++ b/_ru/scala3/book/fun-summary.md
@@ -9,7 +9,7 @@ description: На этой странице представлен обзор п
language: ru
num: 34
previous-page: fun-write-method-returns-function
-next-page:
+next-page: packaging-imports
---
Это была длинная глава, поэтому давайте рассмотрим ключевые моменты, которые мы прошли.
diff --git a/_ru/scala3/book/methods-main-methods.md b/_ru/scala3/book/methods-main-methods.md
index 3faca33023..79fc169c54 100644
--- a/_ru/scala3/book/methods-main-methods.md
+++ b/_ru/scala3/book/methods-main-methods.md
@@ -175,4 +175,4 @@ $ scala happyBirthday 23 Lisa Peter
Happy 23rd Birthday, Lisa and Peter!
```
-[given]: {% link _overviews/scala3-book/ca-given-using-clauses.md %}
+[given]: {% link _overviews/scala3-book/ca-context-parameters.md %}
diff --git a/_ru/scala3/book/packaging-imports.md b/_ru/scala3/book/packaging-imports.md
new file mode 100644
index 0000000000..bde9efdd30
--- /dev/null
+++ b/_ru/scala3/book/packaging-imports.md
@@ -0,0 +1,418 @@
+---
+layout: multipage-overview
+title: Пакеты и импорт
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: chapter
+description: Обсуждение использования пакетов и импорта для организации кода, создания связанных модулей кода, управления областью действия и предотвращения конфликтов пространств имен.
+language: ru
+num: 35
+previous-page: fun-summary
+next-page: collections-intro
+---
+
+
+Scala использует _packages_ для создания пространств имен, которые позволяют модульно разбивать программы.
+Scala поддерживает стиль именования пакетов, используемый в Java, а также нотацию пространства имен “фигурные скобки”,
+используемую такими языками, как C++ и C#.
+
+Подход Scala к импорту похож на Java, но более гибкий.
+С помощью Scala можно:
+
+- импортировать пакеты, классы, объекты, trait-ы и методы
+- размещать операторы импорта в любом месте
+- скрывать и переименовывать участников при импорте
+
+Эти особенности демонстрируются в следующих примерах.
+
+
+## Создание пакета
+
+Пакеты создаются путем объявления одного или нескольких имен пакетов в начале файла Scala.
+Например, если ваше доменное имя _acme.com_ и вы работаете с пакетом _model_ приложения с именем _myapp_,
+объявление пакета выглядит следующим образом:
+
+```scala
+package com.acme.myapp.model
+
+class Person ...
+```
+
+По соглашению все имена пакетов должны быть строчными,
+а формальным соглашением об именах является _\.\.\.\_.
+
+Хотя это и не обязательно, имена пакетов обычно совпадают с именами иерархии каталогов.
+Поэтому, если следовать этому соглашению, класс `Person` в этом проекте будет найден
+в файле _MyApp/src/main/scala/com/acme/myapp/model/Person.scala_.
+
+
+### Использование нескольких пакетов в одном файле
+
+Показанный выше синтаксис применяется ко всему исходному файлу:
+все определения в файле `Person.scala` принадлежат пакету `com.acme.myapp.model`
+в соответствии с предложением `package` в начале файла.
+
+В качестве альтернативы можно написать `package`, которые применяются только к содержащимся в них определениям:
+
+```scala
+package users:
+
+ package administrators: // полное имя пакета - users.administrators
+ class AdminUser // полное имя класса - users.administrators.AdminUser
+
+ package normalusers: // полное имя пакета - users.normalusers
+ class NormalUser // полное имя класса - users.normalusers.NormalUser
+```
+
+Обратите внимание, что за именами пакетов следует двоеточие, а определения внутри пакета имеют отступ.
+
+Преимущество этого подхода заключается в том, что он допускает вложение пакетов
+и обеспечивает более очевидный контроль над областью видимости и инкапсуляцией, особенно в пределах одного файла.
+
+
+## Операторы импорта
+
+Операторы импорта используются для доступа к сущностям в других пакетах.
+Операторы импорта делятся на две основные категории:
+
+- импорт классов, трейтов, объектов, функций и методов
+- импорт `given` предложений
+
+Первая категория операторов импорта аналогична тому, что использует Java,
+с немного другим синтаксисом, обеспечивающим большую гибкость.
+Пример:
+
+````
+import users.* // импортируется все из пакета `users`
+import users.User // импортируется только класс `User`
+import users.{User, UserPreferences} // импортируются только два члена пакета
+import users.{UserPreferences as UPrefs} // переименование импортированного члена
+````
+
+Эти примеры предназначены для того, чтобы дать представление о том, как работает первая категория операторов `import`.
+Более подробно они объясняются в следующих подразделах.
+
+Операторы импорта также используются для импорта `given` экземпляров в область видимости.
+Они обсуждаются в конце этой главы.
+
+> import не требуется для доступа к членам одного и того же пакета.
+
+
+### Импорт одного или нескольких членов
+
+В Scala импортировать один элемент из пакета можно следующим образом:
+
+```scala
+import scala.concurrent.Future
+```
+
+несколько:
+
+```scala
+import scala.concurrent.Future
+import scala.concurrent.Promise
+import scala.concurrent.blocking
+```
+
+При импорте нескольких элементов их можно импортировать более лаконично:
+
+```scala
+import scala.concurrent.{Future, Promise, blocking}
+```
+
+Если необходимо импортировать все из пакета _scala.concurrent_, используется такой синтаксис:
+
+```scala
+import scala.concurrent.*
+```
+
+
+### Переименование элементов при импорте
+
+Иногда необходимо переименовать объекты при их импорте, чтобы избежать конфликтов имен.
+Например, если нужно использовать Scala класс `List` вместе с `java.util.List`,
+то можно переименовать `java.util.List` при импорте:
+
+```scala
+import java.util.{List as JavaList}
+```
+
+Теперь имя `JavaList` можно использовать для ссылки на класс `java.util.List`
+и использовать `List` для ссылки на Scala класс `List`.
+
+Также можно переименовывать несколько элементов одновременно, используя следующий синтаксис:
+
+```scala
+import java.util.{Date as JDate, HashMap as JHashMap, *}
+```
+
+В этой строке кода говорится следующее: “Переименуйте классы `Date` и `HashMap`, как показано,
+и импортируйте все остальное из пакета `java.util`, не переименовывая”.
+
+
+### Скрытие членов при импорте
+
+При импорте часть объектов можно _скрывать_.
+Следующий оператор импорта скрывает класс `java.util.Random`,
+в то время как все остальное в пакете `java.util` импортируется:
+
+```scala
+import java.util.{Random as _, *}
+```
+
+Если попытаться получить доступ к классу `Random`, то выдается ошибка,
+но есть доступ ко всем остальным членам пакета `java.util`:
+
+```scala
+val r = new Random // не скомпилируется
+new ArrayList // доступ есть
+```
+
+#### Скрытие нескольких элементов
+
+Чтобы скрыть в import несколько элементов, их можно перечислить перед использованием постановочного знака:
+
+```scala
+scala> import java.util.{List as _, Map as _, Set as _, *}
+```
+
+Перечисленные классы скрыты, но можно использовать все остальное в _java.util_:
+
+```scala
+scala> new ArrayList[String]
+val res0: java.util.ArrayList[String] = []
+```
+
+Поскольку эти Java классы скрыты, можно использовать классы Scala `List`, `Set` и `Map` без конфликта имен:
+
+```scala
+scala> val a = List(1, 2, 3)
+val a: List[Int] = List(1, 2, 3)
+
+scala> val b = Set(1, 2, 3)
+val b: Set[Int] = Set(1, 2, 3)
+
+scala> val c = Map(1 -> 1, 2 -> 2)
+val c: Map[Int, Int] = Map(1 -> 1, 2 -> 2)
+```
+
+
+### Импорт можно использовать в любом месте
+
+В Scala операторы `import` могут быть объявлены где угодно.
+Их можно использовать в верхней части файла исходного кода:
+
+```scala
+package foo
+
+import scala.util.Random
+
+class ClassA:
+ def printRandom(): Unit =
+ val r = new Random // класс Random здесь доступен
+ // ещё код...
+```
+
+Также операторы `import` можно использовать ближе к тому месту, где они необходимы:
+
+```scala
+package foo
+
+class ClassA:
+ import scala.util.Random // внутри ClassA
+ def printRandom(): Unit =
+ val r = new Random
+ // ещё код...
+
+class ClassB:
+ // класс Random здесь невидим
+ val r = new Random // этот код не скомпилится
+```
+
+
+### “Статический” импорт
+
+Если необходимо импортировать элементы способом, аналогичным подходу “статического импорта” в Java,
+то есть для того, чтобы напрямую обращаться к членам класса, не добавляя к ним префикс с именем класса,
+используется следующий подход.
+
+Синтаксис для импорта всех статических членов Java класса `Math`:
+
+```scala
+import java.lang.Math.*
+```
+
+Теперь можно получить доступ к статическим методам класса `Math`,
+таким как `sin` и `cos`, без необходимости предварять их именем класса:
+
+```scala
+import java.lang.Math.*
+
+val a = sin(0) // 0.0
+val b = cos(PI) // -1.0
+```
+
+
+### Пакеты, импортированные по умолчанию
+
+Два пакета неявно импортируются во все файлы исходного кода:
+
+- `java.lang.*`
+- `scala.*`
+
+Члены object `Predef` также импортируются по умолчанию.
+
+> Например, такие классы, как `List`, `Vector`, `Map` и т.д. можно использовать явно, не импортируя их -
+> они доступны, потому что определены в object `Predef`
+
+
+### Обработка конфликтов имен
+
+Если необходимо импортировать что-то из корня проекта и возникает конфликт имен,
+достаточно просто добавить к имени пакета префикс `_root_`:
+
+```
+package accounts
+
+import _root_.accounts.*
+```
+
+
+## Импорт экземпляров `given`
+
+Как будет показано в главе [“Контекстные абстракции”][contextual],
+для импорта экземпляров `given` используется специальная форма оператора `import`.
+Базовая форма показана в этом примере:
+
+```scala
+object A:
+ class TC
+ given tc as TC
+ def f(using TC) = ???
+
+object B:
+ import A.* // импорт всех non-given членов
+ import A.given // импорт экземпляров given
+```
+
+В этом коде предложение `import A.*` объекта `B` импортирует все элементы `A`, _кроме_ `given` экземпляра `tc`.
+И наоборот, второй импорт, `import A.given`, импортирует _только_ `given` экземпляр.
+Два предложения импорта также могут быть объединены в одно:
+
+```scala
+object B:
+ import A.{given, *}
+```
+
+### Обсуждение
+
+Селектор с подстановочным знаком `*` помещает в область видимости все определения, кроме `given`,
+тогда как селектор выше помещает в область действия все данные, включая те, которые являются результатом расширений.
+
+Эти правила имеют два основных преимущества:
+
+- более понятно, откуда берутся данные given.
+ В частности, невозможно скрыть импортированные given в длинном списке других импортируемых подстановочных знаков.
+- есть возможность импортировать все given, не импортируя ничего другого.
+ Это особенно важно, поскольку given могут быть анонимными, поэтому обычное использование именованного импорта нецелесообразно.
+
+
+### Импорт по типу
+
+Поскольку given-ы могут быть анонимными, не всегда практично импортировать их по имени,
+и вместо этого обычно используется импорт подстановочных знаков.
+_Импорт по типу_ предоставляет собой более конкретную альтернативу импорту с подстановочными знаками,
+делая понятным то, что импортируется.
+
+```scala
+import A.{given TC}
+```
+
+Этот код импортирует из `A` любой `given` тип, соответствующий `TC`.
+Импорт данных нескольких типов `T1,...,Tn` выражается несколькими `given` селекторами:
+
+```scala
+import A.{given T1, ..., given Tn}
+```
+
+Импорт всех `given` экземпляров параметризованного типа достигается аргументами с подстановочными знаками.
+Например, есть такой `объект`:
+
+```scala
+object Instances:
+ given intOrd as Ordering[Int]
+ given listOrd[T: Ordering] as Ordering[List[T]]
+ given ec as ExecutionContext = ...
+ given im as Monoid[Int]
+```
+
+Оператор `import` ниже импортирует экземпляры `intOrd`, `listOrd` и `ec`, но пропускает экземпляр `im`,
+поскольку он не соответствует ни одному из указанных шаблонов:
+
+```scala
+import Instances.{given Ordering[?], given ExecutionContext}
+```
+
+Импорт по типу можно смешивать с импортом по имени.
+Если оба присутствуют в предложении import, импорт по типу идет последним.
+Например, это предложение импорта импортирует `im`, `intOrd` и `listOrd`, но не включает `ec`:
+
+```scala
+import Instances.{im, given Ordering[?]}
+```
+
+
+### Пример
+
+В качестве конкретного примера представим, что у нас есть объект `MonthConversions`,
+который содержит два определения `given`:
+
+```scala
+object MonthConversions:
+ trait MonthConverter[A]:
+ def convert(a: A): String
+
+ given intMonthConverter: MonthConverter[Int] with
+ def convert(i: Int): String =
+ i match
+ case 1 => "January"
+ case 2 => "February"
+ // остальные случаи здесь ...
+
+ given stringMonthConverter: MonthConverter[String] with
+ def convert(s: String): String =
+ s match
+ case "jan" => "January"
+ case "feb" => "February"
+ // остальные случаи здесь ...
+```
+
+Чтобы импортировать эти given-ы в текущую область, используем два оператора `import`:
+
+```scala
+import MonthConversions.*
+import MonthConversions.{given MonthConverter[?]}
+```
+
+Теперь создаем метод, использующий эти экземпляры:
+
+```scala
+def genericMonthConverter[A](a: A)(using monthConverter: MonthConverter[A]): String =
+ monthConverter.convert(a)
+```
+
+Вызов метода:
+
+```scala
+@main def main =
+ println(genericMonthConverter(1)) // January
+ println(genericMonthConverter("jan")) // January
+```
+
+Как уже упоминалось ранее, одно из ключевых преимуществ синтаксиса “import given” состоит в том,
+чтобы прояснить, откуда берутся данные в области действия,
+и в `import` операторах выше ясно, что данные поступают из объекта `MonthConversions`.
+
+
+[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %}
diff --git a/_ru/scala3/book/scala-features.md b/_ru/scala3/book/scala-features.md
index 84dedb717f..c5f6308ac9 100644
--- a/_ru/scala3/book/scala-features.md
+++ b/_ru/scala3/book/scala-features.md
@@ -230,7 +230,7 @@ val z = nums
- [Типы пересечения]({% link _overviews/scala3-book/types-intersection.md %})
- [Типы объединения]({% link _overviews/scala3-book/types-union.md %})
- [Лямбда-типы]({{ site.scala3ref }}/new-types/type-lambdas.html)
-- [Экземпляры `given` и предложения `using`]({% link _overviews/scala3-book/ca-given-using-clauses.md %})
+- [Экземпляры `given` и предложения `using`]({% link _overviews/scala3-book/ca-context-parameters.md %})
- [Методы расширения]({% link _overviews/scala3-book/ca-extension-methods.md %})
- [Типовые классы]({% link _overviews/scala3-book/ca-type-classes.md %})
- [Многостороннее равенство]({% link _overviews/scala3-book/ca-multiversal-equality.md %})
@@ -482,6 +482,6 @@ JSON библиотеки:
[reference]: {{ site.scala3ref }}/overview.html
[multiversal]: {% link _overviews/scala3-book/ca-multiversal-equality.md %}
[extension]: {% link _overviews/scala3-book/ca-extension-methods.md %}
-[givens]: {% link _overviews/scala3-book/ca-given-using-clauses.md %}
+[givens]: {% link _overviews/scala3-book/ca-context-parameters.md %}
[opaque_types]: {% link _overviews/scala3-book/types-opaque-types.md %}
diff --git a/_ru/scala3/book/types-generics.md b/_ru/scala3/book/types-generics.md
new file mode 100644
index 0000000000..799a85e3d5
--- /dev/null
+++ b/_ru/scala3/book/types-generics.md
@@ -0,0 +1,103 @@
+---
+layout: multipage-overview
+title: Параметризованные типы
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: section
+description: В этом разделе представлены параметризованные типы в Scala 3.
+language: ru
+num: 49
+previous-page: types-inferred
+next-page:
+---
+
+Универсальные (_generic_) классы (или trait-ы) принимают тип в качестве _параметра_ в квадратных скобках `[...]`.
+Для обозначения параметров типа согласно конвенции Scala используется одна заглавная буква (например, `A`).
+Затем этот тип можно использовать внутри класса по мере необходимости
+для параметров экземпляра метода или для возвращаемых типов:
+
+{% tabs stack class=tabs-scala-version %}
+
+{% tab 'Scala 2' %}
+
+```scala
+// здесь мы объявляем параметр типа A
+// v
+class Stack[A] {
+ private var elements: List[A] = Nil
+ // ^
+ // здесь мы ссылаемся на этот тип
+ // v
+ def push(x: A): Unit =
+ elements = elements.prepended(x)
+ def peek: A = elements.head
+ def pop(): A = {
+ val currentTop = peek
+ elements = elements.tail
+ currentTop
+ }
+}
+```
+
+{% endtab %}
+
+{% tab 'Scala 3' %}
+
+```scala
+// здесь мы объявляем параметр типа A
+// v
+class Stack[A]:
+ private var elements: List[A] = Nil
+ // ^
+ // здесь мы ссылаемся на этот тип
+ // v
+ def push(x: A): Unit =
+ elements = elements.prepended(x)
+ def peek: A = elements.head
+ def pop(): A =
+ val currentTop = peek
+ elements = elements.tail
+ currentTop
+```
+
+{% endtab %}
+{% endtabs %}
+
+Эта реализация класса `Stack` принимает любой тип в качестве параметра.
+Прелесть параметризованных типов состоит в том,
+что теперь можно создавать `Stack[Int]`, `Stack[String]` и т.д.,
+что позволяет повторно использовать реализацию `Stack` для произвольных типов элементов.
+
+Пример создания и использования `Stack[Int]`:
+
+{% tabs stack-usage class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+
+```scala
+val stack = new Stack[Int]
+stack.push(1)
+stack.push(2)
+println(stack.pop()) // выводит 2
+println(stack.pop()) // выводит 1
+```
+
+{% endtab %}
+
+{% tab 'Scala 3' %}
+
+```scala
+val stack = Stack[Int]
+stack.push(1)
+stack.push(2)
+println(stack.pop()) // выводит 2
+println(stack.pop()) // выводит 1
+```
+
+{% endtab %}
+{% endtabs %}
+
+> Подробности о том, как выразить вариантность с помощью универсальных типов,
+> см. в разделе ["Вариантность"][variance].
+
+[variance]: {% link _overviews/scala3-book/types-variance.md %}
diff --git a/_ru/scala3/book/types-inferred.md b/_ru/scala3/book/types-inferred.md
new file mode 100644
index 0000000000..70c44fcc9d
--- /dev/null
+++ b/_ru/scala3/book/types-inferred.md
@@ -0,0 +1,65 @@
+---
+layout: multipage-overview
+title: Определение типов
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: section
+description: В этом разделе представлены и демонстрируются выводимые типы в Scala 3.
+language: ru
+num: 48
+previous-page: types-introduction
+next-page: types-generics
+---
+
+Как и в других статически типизированных языках программирования,
+в Scala тип можно _объявить_ при создании новой переменной:
+
+{% tabs xy %}
+{% tab 'Scala 2 и 3' %}
+
+```scala
+val x: Int = 1
+val y: Double = 1
+```
+
+{% endtab %}
+{% endtabs %}
+
+В этих примерах типы _явно_ объявлены как `Int` и `Double` соответственно.
+Однако в Scala обычно необязательно указывать тип при объявлении переменной:
+
+{% tabs abm %}
+{% tab 'Scala 2 и 3' %}
+
+```scala
+val a = 1
+val b = List(1, 2, 3)
+val m = Map(1 -> "one", 2 -> "two")
+```
+
+{% endtab %}
+{% endtabs %}
+
+Когда вы это сделаете, Scala сама _выведет_ типы, как показано в следующей сессии REPL:
+
+{% tabs abm2 %}
+{% tab 'Scala 2 и 3' %}
+
+```scala
+scala> val a = 1
+val a: Int = 1
+
+scala> val b = List(1, 2, 3)
+val b: List[Int] = List(1, 2, 3)
+
+scala> val m = Map(1 -> "one", 2 -> "two")
+val m: Map[Int, String] = Map(1 -> one, 2 -> two)
+```
+
+{% endtab %}
+{% endtabs %}
+
+Действительно, большинство переменных определяются без указания типа,
+и способность Scala автоматически определять его — это одна из особенностей,
+которая делает Scala _похожим_ на язык с динамической типизацией.
diff --git a/_ru/scala3/book/types-introduction.md b/_ru/scala3/book/types-introduction.md
new file mode 100644
index 0000000000..49b3cbfb49
--- /dev/null
+++ b/_ru/scala3/book/types-introduction.md
@@ -0,0 +1,68 @@
+---
+layout: multipage-overview
+title: Типы и система типов
+scala3: true
+partof: scala3-book
+overview-name: "Scala 3 — Book"
+type: chapter
+description: В этой главе представлено введение в типы и систему типов Scala 3.
+language: ru
+num: 47
+previous-page: fp-summary
+next-page: types-inferred
+---
+
+Scala — уникальный язык, поскольку он статически типизирован, но часто кажется гибким и динамичным.
+Например, благодаря выводу типов можно писать код без явного указания типов переменных:
+
+{% tabs hi %}
+{% tab 'Scala 2 и 3' %}
+
+```scala
+val a = 1
+val b = 2.0
+val c = "Hi!"
+```
+
+{% endtab %}
+{% endtabs %}
+
+Это делает код динамически типизированным.
+А благодаря новым функциям в Scala 3, таким как [объединение типов][union-types],
+также можно писать код, подобный следующему,
+который кратко выражает, какие значения ожидаются в качестве аргументов и какие типы возвращаются:
+
+{% tabs union-example %}
+{% tab 'Только в Scala 3' %}
+
+```scala
+def isTruthy(a: Boolean | Int | String): Boolean = ???
+def dogCatOrWhatever(): Dog | Plant | Car | Sun = ???
+```
+
+{% endtab %}
+{% endtabs %}
+
+Как видно из примера, при использовании объединения типы необязательно должны иметь общую иерархию,
+и их по-прежнему можно принимать в качестве аргументов или возвращать из метода.
+
+При разработке приложений такие функции, как вывод типов,
+используются каждый день, а generics - каждую неделю.
+При чтении Scaladoc для классов и методов, также необходимо иметь некоторое представление о _ковариантности_.
+Использование типов может быть относительно простым,
+а также обеспечивает большую выразительность, гибкость и контроль для разработчиков библиотек.
+
+## Преимущества типов
+
+Языки программирования со статической типизацией предлагают ряд преимуществ, в том числе:
+
+- помощь IDE в обеспечении надежной поддержки
+- устранение многих классов потенциальных ошибок во время компиляции
+- помощь в рефакторинге
+- предоставление надежной документации, которая не может быть нерелевантной, поскольку проверена на тип
+
+## Знакомство с особенностями системы типов в Scala
+
+Учитывая это краткое введение, в следующих разделах представлен обзор особенностей системы типов в Scala.
+
+[union-types]: {% link _overviews/scala3-book/types-union.md %}
diff --git a/_ru/scala3/book/why-scala-3.md b/_ru/scala3/book/why-scala-3.md
index 02e260d668..6f2d7eb1a1 100644
--- a/_ru/scala3/book/why-scala-3.md
+++ b/_ru/scala3/book/why-scala-3.md
@@ -481,7 +481,7 @@ Scala обладает множеством замечательных функ
Надеемся, вы откроете для себя больше замечательных возможностей Scala по мере использования языка.
[java]: {% link _overviews/scala3-book/interacting-with-java.md %}
-[given]: {% link _overviews/scala3-book/ca-given-using-clauses.md %}
+[given]: {% link _overviews/scala3-book/ca-context-parameters.md %}
[contextual]: {% link _overviews/scala3-book/ca-contextual-abstractions-intro.md %}
[reference]: {{ site.scala3ref }}
[dropped]: {{ site.scala3ref }}/dropped-features
diff --git a/_ru/tour/basics.md b/_ru/tour/basics.md
index 8af52f18fe..ecd5595830 100644
--- a/_ru/tour/basics.md
+++ b/_ru/tour/basics.md
@@ -26,11 +26,22 @@ previous-page: tour-of-scala
## Выражения
Выражения — это вычислимые утверждения.
+
+{% tabs expression %}
+{% tab 'Scala 2 и 3' for=expression %}
+
```scala mdoc
1 + 1
```
+
+{% endtab %}
+{% endtabs %}
+
Вы можете выводить результаты выражений, используя `println`.
+{% tabs println %}
+{% tab 'Scala 2 и 3' for=println %}
+
```scala mdoc
println(1) // 1
println(1 + 1) // 2
@@ -38,48 +49,80 @@ println("Hello!") // Hello!
println("Hello," + " world!") // Hello, world!
```
+{% endtab %}
+{% endtabs %}
+
### Значения
Результаты выражений можно присваивать именам с помощью ключевого слова `val`.
+{% tabs val %}
+{% tab 'Scala 2 и 3' for=val %}
+
```scala mdoc
val x = 1 + 1
println(x) // 2
```
+{% endtab %}
+{% endtabs %}
+
Названные результаты, такие как `x` в примере, называются значениями.
Вызов значения не приводит к его повторному вычислению.
Значения не изменяемы и не могут быть переназначены.
+{% tabs val-error %}
+{% tab 'Scala 2 и 3' for=val-error %}
+
```scala mdoc:fail
x = 3 // Не компилируется.
```
+{% endtab %}
+{% endtabs %}
+
Типы значений могут быть выведены автоматически, но можно и явно указать тип, как показано ниже:
+{% tabs type-inference %}
+{% tab 'Scala 2 и 3' for=type-inference %}
+
```scala mdoc:nest
val x: Int = 1 + 1
```
+{% endtab %}
+{% endtabs %}
+
Обратите внимание, что объявление типа `Int` происходит после идентификатора `x`, следующим за `:`.
### Переменные
Переменные похожи на значения константы, за исключением того, что их можно присваивать заново. Вы можете объявить переменную с помощью ключевого слова `var`.
+{% tabs var %}
+{% tab 'Scala 2 и 3' for=var %}
+
```scala mdoc:nest
var x = 1 + 1
x = 3 // Компилируется потому что "x" объявлен с ключевым словом "var".
println(x * x) // 9
```
+{% endtab %}
+{% endtabs %}
+
Как и в случае со значениями, вы можете явно указать тип, если захотите:
+{% tabs type-inference-2 %}
+{% tab 'Scala 2 и 3' for=type-inference-2 %}
+
```scala mdoc:nest
var x: Int = 1 + 1
```
+{% endtab %}
+{% endtabs %}
## Блоки
@@ -87,6 +130,9 @@ var x: Int = 1 + 1
Результат последнего выражения в блоке будет результатом всего блока в целом.
+{% tabs blocks %}
+{% tab 'Scala 2 и 3' for=blocks %}
+
```scala mdoc
println({
val x = 1 + 1
@@ -94,70 +140,119 @@ println({
}) // 3
```
+{% endtab %}
+{% endtabs %}
+
## Функции
Функции — это выражения, которые принимают параметры.
Вы можете определить анонимную функцию (т.е. без имени), которая возвращает переданное число, прибавив к нему единицу:
+{% tabs anonymous-function %}
+{% tab 'Scala 2 и 3' for=anonymous-function %}
+
```scala mdoc
(x: Int) => x + 1
```
+{% endtab %}
+{% endtabs %}
+
Слева от `=>` находится список параметров. Справа — выражение, связанное с параметрами.
Вы также можете назвать функции.
+{% tabs named-function %}
+{% tab 'Scala 2 и 3' for=named-function %}
+
```scala mdoc
val addOne = (x: Int) => x + 1
println(addOne(1)) // 2
```
+{% endtab %}
+{% endtabs %}
+
Функции могут принимать множество параметров.
+{% tabs multiple-parameters %}
+{% tab 'Scala 2 и 3' for=multiple-parameters %}
+
```scala mdoc
val add = (x: Int, y: Int) => x + y
println(add(1, 2)) // 3
```
+{% endtab %}
+{% endtabs %}
+
Или вообще не принимать никаких параметров.
+{% tabs no-parameters %}
+{% tab 'Scala 2 и 3' for=no-parameters %}
+
```scala mdoc
val getTheAnswer = () => 42
println(getTheAnswer()) // 42
```
+{% endtab %}
+{% endtabs %}
+
## Методы
Методы выглядят и ведут себя очень похоже на функции, но между ними есть несколько принципиальных различий.
-Методы задаются ключевым словом `def`. За `def` следует имя, список параметров, возвращаемый тип и тело.
+Методы задаются ключевым словом `def`. За `def` следует имя, список параметров, возвращаемый тип и тело.
+
+{% tabs method %}
+{% tab 'Scala 2 и 3' for=method %}
```scala mdoc:nest
def add(x: Int, y: Int): Int = x + y
println(add(1, 2)) // 3
```
+{% endtab %}
+{% endtabs %}
+
Обратите внимание, как объявлен возвращаемый тип сразу _после_ списка параметров и двоеточия `: Int`.
Методы могут принимать несколько списков параметров.
+{% tabs multiple-parameter-lists %}
+{% tab 'Scala 2 и 3' for=multiple-parameter-lists %}
+
```scala mdoc
def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier
println(addThenMultiply(1, 2)(3)) // 9
```
+{% endtab %}
+{% endtabs %}
+
Или вообще ни одного списка параметров.
+{% tabs no-parameter-lists %}
+{% tab 'Scala 2 и 3' for=no-parameter-lists %}
+
```scala mdoc
def name: String = System.getProperty("user.name")
println("Hello, " + name + "!")
```
+{% endtab %}
+{% endtabs %}
+
Есть некоторые отличия от функций, но пока что их можно рассматривать как нечто похожее.
Методы также могут иметь многострочные выражения.
+{% tabs get-square-string class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=get-square-string %}
+
```scala mdoc
def getSquareString(input: Double): String = {
val square = input * input
@@ -166,47 +261,115 @@ def getSquareString(input: Double): String = {
println(getSquareString(2.5)) // 6.25
```
+{% endtab %}
+
+{% tab 'Scala 3' for=get-square-string %}
+
+```scala
+def getSquareString(input: Double): String =
+ val square = input * input
+ square.toString
+
+println(getSquareString(2.5)) // 6.25
+```
+
+{% endtab %}
+
+{% endtabs %}
+
Последнее выражение в теле становится возвращаемым значением метода (у Scala есть ключевое слово `return`, но оно практически не используется).
## Классы
Вы можете объявлять классы используя ключевое слово `class`, за которым следует его имя и параметры конструктора.
+{% tabs greeter-definition class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=greeter-definition %}
+
```scala mdoc
class Greeter(prefix: String, suffix: String) {
def greet(name: String): Unit =
println(prefix + name + suffix)
}
```
+
+{% endtab %}
+
+{% tab 'Scala 3' for=greeter-definition %}
+
+```scala
+class Greeter(prefix: String, suffix: String):
+ def greet(name: String): Unit =
+ println(prefix + name + suffix)
+```
+
+{% endtab %}
+
+{% endtabs %}
+
Возвращаемый тип метода `greet` это `Unit`, используется тогда, когда не имеет смысла что-либо возвращать. Аналогично `void` в Java и C. Поскольку каждое выражение Scala должно иметь какое-то значение, то при отсутствии возвращающегося значения возвращается экземпляр типа Unit. Явным образом его можно задать как `()`, он не несет какой-либо информации.
Вы можете создать экземпляр класса, используя ключевое слово `new`.
-```scala mdoc
+{% tabs greeter-usage class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=greeter-usage %}
+
+```scala mdoc:nest
val greeter = new Greeter("Hello, ", "!")
greeter.greet("Scala developer") // Hello, Scala developer!
```
+{% endtab %}
+
+{% tab 'Scala 3' for=greeter-usage %}
+
+```scala
+val greeter = Greeter("Hello, ", "!")
+greeter.greet("Scala developer") // Hello, Scala developer!
+```
+
+{% endtab %}
+
+{% endtabs %}
+
Позже мы рассмотрим классы [подробнее](classes.html).
## Классы-образцы (Case Class)
В Scala есть специальный тип класса, который называется классом-образцом (case class). По умолчанию такие классы неизменны и сравниваются по значению из конструктора. Вы можете объявлять классы-образцы с помощью ключевых слов `case class`.
+{% tabs case-class-definition %}
+{% tab 'Scala 2 и 3' for=case-class-definition %}
+
```scala mdoc
case class Point(x: Int, y: Int)
```
+{% endtab %}
+{% endtabs %}
+
Можно создавать экземпляры класса-образца без использования ключевого слова `new`.
+{% tabs case-class-creation %}
+{% tab 'Scala 2 и 3' for=case-class-creation %}
+
```scala mdoc
val point = Point(1, 2)
val anotherPoint = Point(1, 2)
val yetAnotherPoint = Point(2, 2)
```
+{% endtab %}
+{% endtabs %}
+
Они сравниваются по значению.
+{% tabs compare-case-class-equality class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=compare-case-class-equality %}
+
```scala mdoc
if (point == anotherPoint) {
println(s"$point and $anotherPoint are the same.")
@@ -221,6 +384,28 @@ if (point == yetAnotherPoint) {
} // Point(1,2) и Point(2,2) разные.
```
+{% endtab %}
+
+{% tab 'Scala 3' for=compare-case-class-equality %}
+
+```scala
+if point == anotherPoint then
+ println(s"$point and $anotherPoint are the same.")
+else
+ println(s"$point and $anotherPoint are different.")
+// Point(1,2) и Point(1,2) одни и те же.
+
+if point == yetAnotherPoint then
+ println(s"$point and $yetAnotherPoint are the same.")
+else
+ println(s"$point and $yetAnotherPoint are different.")
+// Point(1,2) и Point(2,2) разные.
+```
+
+{% endtab %}
+
+{% endtabs %}
+
Есть еще много деталей, которые мы бы хотели рассказать про классы-образцы; мы уверены, что вы влюбитесь в них! Обязательно рассмотрим их [позже](case-classes.html).
## Объекты
@@ -229,6 +414,10 @@ if (point == yetAnotherPoint) {
Вы можете задать объекты при помощи ключевого слова `object`.
+{% tabs id-factory-definition class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=id-factory-definition %}
+
```scala mdoc
object IdFactory {
private var counter = 0
@@ -239,8 +428,27 @@ object IdFactory {
}
```
+{% endtab %}
+
+{% tab 'Scala 3' for=id-factory-definition %}
+
+```scala
+object IdFactory:
+ private var counter = 0
+ def create(): Int =
+ counter += 1
+ counter
+```
+
+{% endtab %}
+
+{% endtabs %}
+
Вы можете сразу получить доступ к объекту, ссылаясь на его имя.
+{% tabs id-factory-usage %}
+{% tab 'Scala 2 и 3' for=id-factory-usage %}
+
```scala mdoc
val newId: Int = IdFactory.create()
println(newId) // 1
@@ -248,6 +456,9 @@ val newerId: Int = IdFactory.create()
println(newerId) // 2
```
+{% endtab %}
+{% endtabs %}
+
Позже мы рассмотрим объекты [подробнее](singleton-objects.html).
## Трейты
@@ -256,14 +467,35 @@ println(newerId) // 2
Объявить трейт можно с помощью ключевого слова `trait`.
+{% tabs greeter-trait-def class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=greeter-trait-def %}
+
```scala mdoc:nest
trait Greeter {
def greet(name: String): Unit
}
```
+{% endtab %}
+
+{% tab 'Scala 3' for=greeter-trait-def %}
+
+```scala
+trait Greeter:
+ def greet(name: String): Unit
+```
+
+{% endtab %}
+
+{% endtabs %}
+
Трейты также могут иметь реализации методов и полей, которые предполагается использовать умолчанию.
+{% tabs greeter-trait-def-impl class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=greeter-trait-def-impl %}
+
```scala mdoc:reset
trait Greeter {
def greet(name: String): Unit =
@@ -271,8 +503,26 @@ trait Greeter {
}
```
+{% endtab %}
+
+{% tab 'Scala 3' for=greeter-trait-def-impl %}
+
+```scala
+trait Greeter:
+ def greet(name: String): Unit =
+ println("Hello, " + name + "!")
+```
+
+{% endtab %}
+
+{% endtabs %}
+
Вы можете наследовать свойства трейтов, используя ключевое слово `extends` и переопределять реализацию с помощью ключевого слова `override`.
+{% tabs greeter-implementations class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=greeter-implementations %}
+
```scala mdoc
class DefaultGreeter extends Greeter
@@ -289,6 +539,28 @@ val customGreeter = new CustomizableGreeter("How are you, ", "?")
customGreeter.greet("Scala developer") // How are you, Scala developer?
```
+{% endtab %}
+
+{% tab 'Scala 3' for=greeter-implementations %}
+
+```scala
+class DefaultGreeter extends Greeter
+
+class CustomizableGreeter(prefix: String, postfix: String) extends Greeter:
+ override def greet(name: String): Unit =
+ println(prefix + name + postfix)
+
+val greeter = DefaultGreeter()
+greeter.greet("Scala developer") // Hello, Scala developer!
+
+val customGreeter = CustomizableGreeter("How are you, ", "?")
+customGreeter.greet("Scala developer") // How are you, Scala developer?
+```
+
+{% endtab %}
+
+{% endtabs %}
+
Здесь `DefaultGreeter` наследуется только от одного трейта, но можно наследоваться от нескольких.
Позже мы рассмотрим трейты [подробнее](traits.html).
@@ -300,9 +572,33 @@ customGreeter.greet("Scala developer") // How are you, Scala developer?
Используя объект, можно задать главный метод следующим образом:
+{% tabs hello-world-demo class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=hello-world-demo %}
+
+In Scala 2 you must define a main method manually. Using an object, you can define the main method as follows:
+
```scala mdoc
object Main {
def main(args: Array[String]): Unit =
println("Hello, Scala developer!")
}
```
+
+{% endtab %}
+
+{% tab 'Scala 3' for=hello-world-demo %}
+
+In Scala 3, with the `@main` annotation, a main method is automatically generated from a method as follows:
+
+```scala
+@main def hello() = println("Hello, Scala developer!")
+```
+
+{% endtab %}
+
+{% endtabs %}
+
+## Дополнительные ресурсы
+
+- Обзор [Scala book](/ru/scala3/book/taste-intro.html)
diff --git a/_ru/tour/classes.md b/_ru/tour/classes.md
index d7e66a84c0..75701bb107 100644
--- a/_ru/tour/classes.md
+++ b/_ru/tour/classes.md
@@ -13,13 +13,41 @@ prerequisite-knowledge: no-return-keyword, type-declaration-syntax, string-inter
Классы в Scala являются основами для создания объектов. Они могут содержать методы, константы, переменные, типы, объекты, трейты и классы, которые в совокупности называются _членами_. Типы, объекты и трейты будут рассмотрены позже в ходе нашего обзора.
## Объявление класса
+
Минимальное объявление класса - это просто ключевое слово `class` и его имя. Имена классов должны быть написаны с заглавной буквы.
+
+{% tabs class-minimal-user class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=class-minimal-user %}
+
```scala mdoc
class User
val user1 = new User
```
-Ключевое слово `new` используется для создания экземпляра класса. `User` имеет конструктор по умолчанию, который не принимает аргументов, так как конструктор не был определен. Однако обычно используется и конструктор, и тело класса. Пример объявления класса Point приведен ниже:
+
+Ключевое слово `new` используется для создания экземпляра класса.
+{% endtab %}
+
+{% tab 'Scala 3' for=class-minimal-user %}
+
+```scala
+class User
+
+val user1 = User()
+```
+
+Чтобы создать экземпляр класса, мы вызываем его как функцию: `User()`.
+Также можно явно использовать ключевое слово `new`: `new User()` - хотя обычно это опускается.
+{% endtab %}
+
+{% endtabs %}
+
+`User` имеет конструктор по умолчанию, который не принимает аргументов, так как конструктор не был определен. Однако обычно используется и конструктор, и тело класса. Пример объявления класса Point приведен ниже:
+
+{% tabs class-point-example class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=class-point-example %}
```scala mdoc
class Point(var x: Int, var y: Int) {
@@ -34,10 +62,34 @@ class Point(var x: Int, var y: Int) {
}
val point1 = new Point(2, 3)
-point1.x // 2
-println(point1) // prints (2, 3)
+println(point1.x) // выводит 2
+println(point1) // выводит (2, 3)
+```
+
+{% endtab %}
+
+{% tab 'Scala 3' for=class-point-example %}
+
+```scala
+class Point(var x: Int, var y: Int):
+
+ def move(dx: Int, dy: Int): Unit =
+ x = x + dx
+ y = y + dy
+
+ override def toString: String =
+ s"($x, $y)"
+end Point
+
+val point1 = Point(2, 3)
+println(point1.x) // выводит 2
+println(point1) // выводит (2, 3)
```
+{% endtab %}
+
+{% endtabs %}
+
В этом классе у `Point` есть четыре члена: переменные `x` и `y` и методы `move` и `toString`.
В отличие от многих других языков, основной конструктор находится в сигнатуре класса `(var x: Int, var y: Int)`. Метод `move` принимает два целочисленных аргумента и возвращает значение Unit `()` - это пустое множество, которое не содержит никакой информации. Примерно соответствует `void` в Java-подобных языках. С другой стороны, `toString` не принимает никаких аргументов, а возвращает значение `String`. Поскольку `toString` переопределяет `toString` из [`AnyRef`](unified-types.html), он помечается ключевым словом `override`.
@@ -45,61 +97,193 @@ println(point1) // prints (2, 3)
Конструкторы могут иметь необязательные параметры, если указать их значения по умолчанию как в примере:
+{% tabs class-point-with-default-values class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=class-point-with-default-values %}
+
```scala mdoc:nest
class Point(var x: Int = 0, var y: Int = 0)
-val origin = new Point // x и y оба равны 0
-val point1 = new Point(1)
-println(point1.x) // выводит 1
+val origin = new Point // x и y оба равны 0
+val point1 = new Point(1) // x равен 1, а y равен 0
+println(point1) // выводит (1, 0)
+```
+
+{% endtab %}
+
+{% tab 'Scala 3' for=class-point-with-default-values %}
+
+```scala
+class Point(var x: Int = 0, var y: Int = 0)
+val origin = Point() // x и y оба равны 0
+val point1 = Point(1) // x равен 1, а y равен 0
+println(point1) // выводит (1, 0)
```
+{% endtab %}
+
+{% endtabs %}
+
В этой версии класса `Point`, `x` и `y` имеют значение по умолчанию `0`, поэтому аргументов не требуется. Однако, поскольку конструктор считывает аргументы слева направо, если вы просто хотите передать значение `y`, то вам нужно будет указать задаваемый параметр.
+
+{% tabs class-point-named-argument class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=class-point-named-argument %}
+
```scala mdoc:nest
class Point(var x: Int = 0, var y: Int = 0)
-val point2 = new Point(y=2)
-println(point2.y) // выводит 2
+val point2 = new Point(y = 2)
+println(point2) // выводит (0, 2)
```
+{% endtab %}
+
+{% tab 'Scala 3' for=class-point-named-argument %}
+
+```scala
+class Point(var x: Int = 0, var y: Int = 0)
+val point2 = Point(y = 2)
+println(point2) // выводит (0, 2)
+```
+
+{% endtab %}
+
+{% endtabs %}
+
Что также является хорошей практикой для повышения ясности кода.
## Скрытые члены и синтаксис Геттер/Сеттер (получатель/установщик значений)
+
По умолчанию члены класса являются открытыми для внешнего доступа (публичными). Используйте модификатор `private`, чтобы скрыть их от внешнего доступа.
-```scala mdoc:nest
+
+{% tabs class-point-private-getter-setter class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=class-point-private-getter-setter %}
+
+```scala mdoc:reset
class Point {
private var _x = 0
private var _y = 0
private val bound = 100
- def x = _x
- def x_= (newValue: Int): Unit = {
- if (newValue < bound) _x = newValue else printWarning
+ def x: Int = _x
+ def x_=(newValue: Int): Unit = {
+ if (newValue < bound)
+ _x = newValue
+ else
+ printWarning()
}
- def y = _y
- def y_= (newValue: Int): Unit = {
- if (newValue < bound) _y = newValue else printWarning
+ def y: Int = _y
+ def y_=(newValue: Int): Unit = {
+ if (newValue < bound)
+ _y = newValue
+ else
+ printWarning()
}
- private def printWarning = println("WARNING: Out of bounds")
+ private def printWarning(): Unit =
+ println("WARNING: Out of bounds")
}
val point1 = new Point
point1.x = 99
point1.y = 101 // выводит предупреждение (printWarning)
```
-В данной версии класса `Point` данные хранятся в скрытых переменных `_x` и `_y`. Существуют методы `def x` и `def y` для доступа к скрытым данным. Методы `def x_=` и `def y_=` (сеттеры) предназначены для проверки и установки значения `_x` и `_y`. Обратите внимание на специальный синтаксис для сеттеров: метод `_=` применяется к имени геттера.
+
+{% endtab %}
+
+{% tab 'Scala 3' for=class-point-private-getter-setter %}
+
+```scala
+class Point:
+ private var _x = 0
+ private var _y = 0
+ private val bound = 100
+
+ def x: Int = _x
+ def x_=(newValue: Int): Unit =
+ if newValue < bound then
+ _x = newValue
+ else
+ printWarning()
+
+ def y: Int = _y
+ def y_=(newValue: Int): Unit =
+ if newValue < bound then
+ _y = newValue
+ else
+ printWarning()
+
+ private def printWarning(): Unit =
+ println("WARNING: Out of bounds")
+end Point
+
+val point1 = Point()
+point1.x = 99
+point1.y = 101 // выводит предупреждение (printWarning)
+```
+
+{% endtab %}
+
+{% endtabs %}
+
+В данной версии класса `Point` данные хранятся в скрытых переменных `_x` и `_y`. Существуют методы `def x` и `def y` для доступа к скрытым данным. Методы `def x_=` и `def y_=` (сеттеры) предназначены для проверки и установки значения `_x` и `_y`. Обратите внимание на специальный синтаксис для сеттеров: метод `_=` применяется к имени геттера.
Первичные параметры конструктора с параметрами `val` и `var` являются общедоступными. Однако, поскольку `val` - это константа, то нельзя писать следующее.
+
+{% tabs class-point-cannot-set-val class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=class-point-cannot-set-val %}
+
```scala mdoc:fail
class Point(val x: Int, val y: Int)
val point = new Point(1, 2)
point.x = 3 // <-- не компилируется
```
+{% endtab %}
+
+{% tab 'Scala 3' for=class-point-cannot-set-val %}
+
+```scala
+class Point(val x: Int, val y: Int)
+val point = Point(1, 2)
+point.x = 3 // <-- не компилируется
+```
+
+{% endtab %}
+
+{% endtabs %}
+
Параметры без `val` или `var` являются скрытыми от внешнего доступа и видимы только внутри класса.
+
+{% tabs class-point-non-val-ctor-param class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=class-point-non-val-ctor-param %}
+
```scala mdoc:fail
class Point(x: Int, y: Int)
val point = new Point(1, 2)
point.x // <-- не компилируется
```
+
+{% endtab %}
+
+{% tab 'Scala 3' for=class-point-non-val-ctor-param %}
+
+```scala
+class Point(x: Int, y: Int)
+val point = Point(1, 2)
+point.x // <-- не компилируется
+```
+
+{% endtab %}
+
+{% endtabs %}
+
+## Дополнительные ресурсы
+
+- Узнайте больше о классах в [Scala Book](/ru/scala3/book/domain-modeling-tools.html#классы)
+- Как использовать [вспомогательные конструкторы классов](/ru/scala3/book/domain-modeling-tools.html#вспомогательные-конструкторы)
diff --git a/_ru/tour/default-parameter-values.md b/_ru/tour/default-parameter-values.md
index ac8c04eda4..c45b5dd419 100644
--- a/_ru/tour/default-parameter-values.md
+++ b/_ru/tour/default-parameter-values.md
@@ -9,7 +9,10 @@ previous-page: classes
prerequisite-knowledge: named-arguments, function syntax
---
-Scala предоставляет возможность задавать значения параметров по умолчанию, что позволяет лишний раз не указывать параметры.
+Scala предоставляет возможность задавать значения параметров по умолчанию, что позволяет лишний раз не указывать параметры.
+
+{% tabs default-parameter-values-1 %}
+{% tab 'Scala 2 и 3' for=default-parameter-values-1 %}
```scala mdoc
def log(message: String, level: String = "INFO") = println(s"$level: $message")
@@ -18,22 +21,41 @@ log("System starting") // выведет "INFO: System starting"
log("User not found", "WARNING") // выведет "WARNING: User not found"
```
+{% endtab %}
+{% endtabs %}
+
У параметра `level` есть значение по умолчанию, поэтому он необязателен. В последней строке аргумент `"WARNING"` переназначает аргумент по умолчанию `"INFO"`. Вместо того чтоб использовать перегруженные методы в Java, вы можете просто указать дополнительные параметры как параметры по умолчанию для достижения того же эффекта. Однако, если при вызове пропущен хотя бы один аргумент, все остальные аргументы должны вызываться с указанием конкретного имени аргумента.
+{% tabs default-parameter-values-2 %}
+{% tab 'Scala 2 и 3' for=default-parameter-values-2 %}
+
```scala mdoc
class Point(val x: Double = 0, val y: Double = 0)
val point1 = new Point(y = 1)
```
+
+{% endtab %}
+{% endtabs %}
+
Так мы можем указать что `y = 1`.
Обратите внимание, что параметры по умолчанию в Scala, при вызове из Java кода, являются обязательными:
+{% tabs default-parameter-values-3 %}
+{% tab 'Scala 2 и 3' for=default-parameter-values-3 %}
+
```scala mdoc:reset
// Point.scala
class Point(val x: Double = 0, val y: Double = 0)
```
+{% endtab %}
+{% endtabs %}
+
+{% tabs default-parameter-values-4 %}
+{% tab 'Java' for=default-parameter-values-4 %}
+
```java
// Main.java
public class Main {
@@ -42,3 +64,37 @@ public class Main {
}
}
```
+
+{% endtab %}
+{% endtabs %}
+
+### Параметры по умолчанию для перегруженных методов
+
+Scala не позволяет определять два метода с параметрами по умолчанию и с одинаковым именем (перегруженные методы).
+Важная причина этого - избежание двусмысленности, которая может быть вызвана наличием параметров по умолчанию.
+Чтобы проиллюстрировать проблему, давайте рассмотрим определение методов, представленных ниже:
+
+{% tabs default-parameter-values-5 class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+
+```scala mdoc:fail
+object A {
+ def func(x: Int = 34): Unit
+ def func(y: String = "abc"): Unit
+}
+```
+
+{% endtab %}
+{% tab 'Scala 3' %}
+
+```scala
+object A:
+ def func(x: Int = 34): Unit
+ def func(y: String = "abc"): Unit
+```
+
+{% endtab %}
+{% endtabs %}
+
+Если мы вызываем `A.func()`, компилятор не может узнать,
+намеревался ли программист вызвать `func(x: Int = 34)` или `func(y: String = "abc")`.
diff --git a/_ru/tour/implicit-conversions.md b/_ru/tour/implicit-conversions.md
index 02c5319fae..77eb2c9c46 100644
--- a/_ru/tour/implicit-conversions.md
+++ b/_ru/tour/implicit-conversions.md
@@ -48,7 +48,7 @@ implicit def int2Integer(x: Int) =
java.lang.Integer.valueOf(x)
```
-Компилятор предупреждает при компиляции об обнаружении неявных преобразований, тк неявные преобразования могут иметь разные подводные камни (особенно если использовать их без разбора).
+Компилятор предупреждает при компиляции об обнаружении неявных преобразований, т.к. неявные преобразования могут иметь разные подводные камни (особенно если использовать их без разбора).
Чтоб отключить предупреждения выполните одно из следующих действий:
diff --git a/_ru/tour/named-arguments.md b/_ru/tour/named-arguments.md
index 463480e547..5b2376cf4b 100644
--- a/_ru/tour/named-arguments.md
+++ b/_ru/tour/named-arguments.md
@@ -11,19 +11,38 @@ prerequisite-knowledge: function-syntax
При вызове методов можно конкретно указывать название задаваемого аргумента следующим образом:
+{% tabs named-arguments-when-good %}
+
+{% tab 'Scala 2 и 3' for=named-arguments-when-good %}
+
```scala mdoc
-def printName(first: String, last: String): Unit = {
+def printName(first: String, last: String): Unit =
println(first + " " + last)
-}
-printName("John", "Smith") // Prints "John Smith"
-printName(first = "John", last = "Smith") // Prints "John Smith"
-printName(last = "Smith", first = "John") // Prints "John Smith"
+printName("John", "Smith") // выводит "John Smith"
+printName(first = "John", last = "Smith") // выводит "John Smith"
+printName(last = "Smith", first = "John") // выводит "John Smith"
```
-Обратите внимание, что при указании имени параметра, порядок аргумента может быть изменен. Однако если какие-то аргументы именованные, а другие нет, то аргументы без имени должны стоять на первом месте и располагаться в том порядке, в котором описаны параметры метода.
+
+{% endtab %}
+
+{% endtabs %}
+
+Обратите внимание, что при указании имени параметра, порядок аргумента может быть изменен.
+Однако если какие-то аргументы именованные, а другие нет,
+то аргументы без имени должны стоять на первом месте и располагаться в том порядке, в котором описаны параметры метода.
+
+{% tabs named-arguments-when-error %}
+
+{% tab 'Scala 2 и 3' for=named-arguments-when-error %}
```scala mdoc:fail
printName(last = "Smith", "john") // ошибка: позиция после именованного аргумента
```
-Обратите внимание, что именованные аргументы не работают при вызове Java методов.
+{% endtab %}
+
+{% endtabs %}
+
+Именованные аргументы работают при вызове Java методов, но только в том случае,
+если используемая Java библиотека была скомпилирована с `-parameters`.
diff --git a/_ru/tour/traits.md b/_ru/tour/traits.md
index ab7f6a5ade..97e2cec5bb 100644
--- a/_ru/tour/traits.md
+++ b/_ru/tour/traits.md
@@ -13,13 +13,25 @@ prerequisite-knowledge: expressions, classes, generics, objects, companion-objec
Трейты (Traits) используются, чтобы обмениваться между классами информацией о структуре и полях. Они похожи на интерфейсы из Java 8. Классы и объекты могут расширять трейты, но трейты не могут быть созданы и поэтому не имеют параметров.
## Объявление трейта
+
Минимальное объявление трейта - это просто ключевое слово `trait` и его имя:
+{% tabs trait-hair-color %}
+{% tab 'Scala 2 и 3' for=trait-hair-color %}
+
```scala mdoc
trait HairColor
```
+{% endtab %}
+{% endtabs %}
+
Трейты наиболее полезны в качестве обобщенного типа с абстрактными методами.
+
+{% tabs trait-iterator-definition class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=trait-iterator-definition %}
+
```scala mdoc
trait Iterator[A] {
def hasNext: Boolean
@@ -27,10 +39,30 @@ trait Iterator[A] {
}
```
+{% endtab %}
+
+{% tab 'Scala 3' for=trait-iterator-definition %}
+
+```scala
+trait Iterator[A]:
+ def hasNext: Boolean
+ def next(): A
+```
+
+{% endtab %}
+
+{% endtabs %}
+
При наследовании от трейта `Iterator[A]` требует указание типа `A` а также реализация методов `hasNext` и `next`.
## Использование трейтов
+
Чтобы использовать трейты, необходимо наследовать класс от него, используя ключевое слово `extends`. Затем необходимо реализовать все абстрактные члены трейта, используя ключевое слово `override`:
+
+{% tabs trait-intiterator-definition class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=trait-intiterator-definition %}
+
```scala mdoc:nest
trait Iterator[A] {
def hasNext: Boolean
@@ -40,7 +72,7 @@ trait Iterator[A] {
class IntIterator(to: Int) extends Iterator[Int] {
private var current = 0
override def hasNext: Boolean = current < to
- override def next(): Int = {
+ override def next(): Int = {
if (hasNext) {
val t = current
current += 1
@@ -49,15 +81,51 @@ class IntIterator(to: Int) extends Iterator[Int] {
}
}
+val iterator = new IntIterator(10)
+iterator.next() // вернет 0
+iterator.next() // вернет 1
+```
+
+{% endtab %}
+
+{% tab 'Scala 3' for=trait-intiterator-definition %}
+
+```scala
+trait Iterator[A]:
+ def hasNext: Boolean
+ def next(): A
+
+class IntIterator(to: Int) extends Iterator[Int]:
+ private var current = 0
+ override def hasNext: Boolean = current < to
+ override def next(): Int =
+ if hasNext then
+ val t = current
+ current += 1
+ t
+ else
+ 0
+end IntIterator
val iterator = new IntIterator(10)
iterator.next() // вернет 0
iterator.next() // вернет 1
```
+
+{% endtab %}
+
+{% endtabs %}
+
Этот класс `IntIterator` использует параметр `to` в качестве верхней границы. Он наследуется от `Iterator[Int]`, что означает, что метод `next` должен возвращать Int.
## Подтипы
-Туда, где требуется определенный тип трейта, мы можем передавать любой наследованный от требуемого трейта класс
+
+Туда, где требуется определенный тип трейта, мы можем передавать любой наследованный от требуемого трейта класс
+
+{% tabs trait-pet-example class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=trait-pet-example %}
+
```scala mdoc
import scala.collection.mutable.ArrayBuffer
@@ -76,4 +144,36 @@ animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name)) // выведет "Harry" и "Sally"
```
+
+{% endtab %}
+
+{% tab 'Scala 3' for=trait-pet-example %}
+
+```scala
+import scala.collection.mutable.ArrayBuffer
+
+trait Pet:
+ val name: String
+
+class Cat(val name: String) extends Pet
+class Dog(val name: String) extends Pet
+
+val dog = Dog("Harry")
+val cat = Cat("Sally")
+
+val animals = ArrayBuffer.empty[Pet]
+animals.append(dog)
+animals.append(cat)
+animals.foreach(pet => println(pet.name)) // выведет "Harry" и "Sally"
+```
+
+{% endtab %}
+
+{% endtabs %}
+
У трейта `Pet` есть абстрактное поле `name`, которое реализовано в классах `Cat` and `Dog`. В последней строке мы вызываем `pet.name`, который должен быть реализован в любом подтипе, унаследованном от трейта `Pet`.
+
+## Дополнительные ресурсы
+
+- Узнайте больше о трейтах в [Scala Book](/scala3/book/domain-modeling-tools.html#traits)
+- Использование трейтов для определения [Enum](/scala3/book/domain-modeling-fp.html#modeling-the-data)
diff --git a/_ru/tour/unified-types.md b/_ru/tour/unified-types.md
index 7b692d363e..e5f947ed89 100644
--- a/_ru/tour/unified-types.md
+++ b/_ru/tour/unified-types.md
@@ -13,7 +13,7 @@ prerequisite-knowledge: classes, basics
-## Иерархия типов Scala ##
+## Иерархия типов Scala
[`Any`](https://www.scala-lang.org/api/2.12.1/scala/Any.html) это супертип всех типов, также называемый верхним типом. Он определяет несколько универсальных методов, таких как `equals`, `hashCode` и `toString`. У `Any` есть два прямых подкласса: `AnyVal` и `AnyRef`.
@@ -23,6 +23,9 @@ prerequisite-knowledge: classes, basics
Вот пример, демонстрирующий, что строки, целые числа, символы, логические значения и функции являются объектами, как и любой другой объект:
+{% tabs unified-types-1 %}
+{% tab 'Scala 2 и 3' for=unified-types-1 %}
+
```scala mdoc
val list: List[Any] = List(
"a string",
@@ -35,6 +38,9 @@ val list: List[Any] = List(
list.foreach(element => println(element))
```
+{% endtab %}
+{% endtabs %}
+
Объявляем переменную `list` типа `List[Any]`. Список инициализируется элементами различных типов, но все они являются экземпляром `scala.Any`, так что вы можете добавить их в список.
Ниже приведен вывод программы:
@@ -48,11 +54,15 @@ true
```
## Приведение типа
+
Числовые типы могут быть приведены следующим образом:
Например:
+{% tabs unified-types-2 %}
+{% tab 'Scala 2 и 3' for=unified-types-2 %}
+
```scala mdoc
val x: Long = 987654321
val y: Float = x.toFloat // 9.8765434E8 (заметьте, что некоторая точность теряется в этом случае.)
@@ -61,17 +71,27 @@ val face: Char = '☺'
val number: Int = face // 9786
```
+{% endtab %}
+{% endtabs %}
+
Приведение типа - однонаправленно. Следующий пример не скомпилируется:
+{% tabs unified-types-3 %}
+{% tab 'Scala 2 и 3' for=unified-types-3 %}
+
```
val x: Long = 987654321
val y: Float = x.toFloat // 9.8765434E8
val z: Long = y // обратно не подходит
```
+{% endtab %}
+{% endtabs %}
+
Вы также можете приводить к своему подтипу. Об этом мы поговорим позже в ходе нашего обзора.
## Nothing и Null
-`Nothing` является подтипом всех типов, также называемым нижним типом. Нет значения, которое имеет тип `Nothing`. Обычно он используется чтоб дать сигнал о не вычислимости, например брошено исключение, выход из программы, бесконечное зацикливание (т.е. это тип выражения, которое не вычисляется).
+
+`Nothing` является подтипом всех типов, также называемым нижним типом. Нет значения, которое имеет тип `Nothing`. Обычно он используется чтоб дать сигнал о не вычислимости, например брошено исключение, выход из программы, бесконечное зацикливание (т.е. это тип выражения, которое не вычисляется).
`Null` подтип всех ссылочных типов (т.е. любой подтип AnyRef). Он имеет одно значение, определяемое ключевым словом литерала `null`. `Null` предоставляется в основном для функциональной совместимости с другими языками JVM и почти никогда не должен использоваться в коде Scala. Об альтернативах `null` мы поговорим позднее.
diff --git a/_ru/tour/variances.md b/_ru/tour/variances.md
index 17dd06b74c..3a56ae2e6d 100644
--- a/_ru/tour/variances.md
+++ b/_ru/tour/variances.md
@@ -8,12 +8,12 @@ next-page: upper-type-bounds
previous-page: generic-classes
---
-Вариантность (Variances) - это указание определенной специфики взаимосвязи между связанными типам. Scala поддерживает вариантную аннотацию типов у [обобщенных классов](generic-classes.html), что позволяет им быть ковариантными, контрвариантными или инвариантными (если нет никакого указание на вариантность). Использование вариантности в системе типов позволяет устанавливать понятные взаимосвязи между сложными типами, в то время как отсутствие вариантности может ограничить повторное использование абстракции класса.
+Вариантность (Variances) - это указание определенной специфики взаимосвязи между связанными типам. Scala поддерживает вариантную аннотацию типов у [обобщенных классов](generic-classes.html), что позволяет им быть ковариантными, контрвариантными или инвариантными (если нет никакого указания на вариантность). Использование вариантности в системе типов позволяет устанавливать понятные взаимосвязи между сложными типами, в то время как отсутствие вариантности может ограничить повторное использование абстракции класса.
```scala mdoc
class Foo[+A] // ковариантный класс
-class Bar[-A] // контрвариантный класс
-class Baz[A] // инвариантными класс
+class Bar[-A] // контравариантный класс
+class Baz[A] // инвариантный класс
```
### Ковариантность
@@ -135,15 +135,15 @@ val cat: Cat = catContainer.getValue // Ой, мы бы закончили пр
Другим примером, который может помочь понять вариантность, является трейт `Function1[-T, +R]` из стандартной библиотеки Scala. `Function1` представляет собой функцию с одним параметром, где первый тип `T` представляет собой тип параметра, а второй тип `R` представляет собой тип результата. Функция `Function1` является контрвариантной в рамках типа принимаемого аргумента, а ковариантной - в рамках возвращаемого типа. Для этого примера мы будем использовать явное обозначение типа `A =>B` чтоб продемонстрировать `Function1[A, B]`.
-Рассмотрим схожий пример `Cat`, `Dog`, `Animal` в той же взаимосвязи что и раньше, плюс следующее:
+Рассмотрим схожий пример `Cat`, `Dog`, `Animal` в той же взаимосвязи, что и раньше, плюс следующее:
```scala mdoc
abstract class SmallAnimal extends Animal
case class Mouse(name: String) extends SmallAnimal
```
-Предположим, мы работаем с функциями, которые принимают типы животных и возвращают типы еды, которую они едят. Если мы хотим `Cat => SmallAnimal` (потому что кошки едят маленьких животных), но вместо этого мы получим функцию `Animal => Mouse`, то наша программа все равно будет работать. Интуитивно функция `Animal => Mouse` все равно будет принимать `Cat` в качестве аргумента, тк `Cat` является `Animal`, и возвращать `Mouse` - который также является и `SmallAnimal`. Поскольку мы можем безопасно заменить первое вторым, можно сказать, что `Animal => Mouse` аналогично `Cat => SmallAnimal`.
+Предположим, что мы работаем с функциями, которые принимают типы животных и возвращают типы еды. Если мы хотим `Cat => SmallAnimal` (потому что кошки едят маленьких животных), но вместо этого мы получим функцию `Animal => Mouse`, то наша программа все равно будет работать. Интуитивно функция `Animal => Mouse` все равно будет принимать `Cat` в качестве аргумента, т.к. `Cat` является `Animal`, и возвращать `Mouse` - который также является и `SmallAnimal`. Поскольку мы можем безопасно заменить первое вторым, можно сказать, что `Animal => Mouse` аналогично `Cat => SmallAnimal`.
### Сравнение с другими языками
-В языках, похожих на Scala, разные способы поддержи вариантности. Например, указания вариантности в Scala очень похожи на то, как это делается в C#, где такие указания добавляются при объявлении абстракции класса (вариантность при объявлении). Однако в Java, указание вариантности задается непосредственно при использовании абстракции класса (вариантность при использовании).
+В языках, похожих на Scala, разные способы поддержки вариантности. Например, указания вариантности в Scala очень похожи на то, как это делается в C#, где такие указания добавляются при объявлении абстракции класса (вариантность при объявлении). Однако в Java, указание вариантности задается непосредственно при использовании абстракции класса (вариантность при использовании).
diff --git a/_sips/process-specification.md b/_sips/process-specification.md
index a3697ded89..186709f245 100644
--- a/_sips/process-specification.md
+++ b/_sips/process-specification.md
@@ -28,13 +28,15 @@ from an idea to the inclusion in the language.
language (additions, changes, and/or removals).
- Committee: a group of experienced Scala practitioners and language designers,
who evaluate changes to the Scala programming language. It consists of a
- Chairperson and Members.
+ Secretary, a Chairperson, and Members.
- Chairperson: person in charge of executing the process. They organize and
chair the meetings of the Committee, and generally make sure the process is
followed, but do not vote on proposals. The Chairperson is an appointed
employee of the Scala Center.
- Committee Member: member of the Committee with voting rights. The Chairperson
cannot be a Member at the same time.
+- Secretary: person attending the regular meetings and responsible for writing
+ notes of the discussions.
- SIP Author: any individual or group of people who writes a SIP for submission
to the Committee. The Chairperson and Committee Members may also be SIP Authors.
Authors are responsible for building consensus within the community and
@@ -268,6 +270,10 @@ The current Chairperson is:
- Julien Richard-Foy ([@julienrf](https://github.com/julienrf)), Scala Center
+The current Secretary is:
+
+- Seth Tisue ([@SethTisue](https://github.com/SethTisue)), Lightbend
+
### Committee Meetings
The plenary Committee Meetings are scheduled monthly by the Chairperson. They
diff --git a/_sips/results/2023-03-17-meeting.md b/_sips/results/2023-03-17-meeting.md
index fd973b4be3..7c1ef1e50f 100644
--- a/_sips/results/2023-03-17-meeting.md
+++ b/_sips/results/2023-03-17-meeting.md
@@ -4,6 +4,6 @@ title: SIP Meeting Results - 17th March 2023
partof: results
proposals:
- url: https://docs.scala-lang.org/sips/scala-cli.html
- name: SIP-46 - Drop Forwards Binary Compatibility of the Scala 2.13 Standard Library
+ name: SIP-46 - Scala CLI as default Scala command
result: accepted
---
diff --git a/_sips/results/2023-04-21-meeting.md b/_sips/results/2023-04-21-meeting.md
new file mode 100644
index 0000000000..b522759d88
--- /dev/null
+++ b/_sips/results/2023-04-21-meeting.md
@@ -0,0 +1,12 @@
+---
+layout: sip-meeting-results
+title: SIP Meeting Results - 21st April 2023
+partof: results
+proposals:
+ - url: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html
+ name: SIP-53 - Quote pattern explicit type variable syntax
+ result: accepted
+ - url: https://docs.scala-lang.org/sips/multi-source-extension-overloads.html
+ name: SIP-54 - Multi-Source Extension Overloads
+ result: accepted
+---
diff --git a/_sips/sips/drop-forwards-binary-compatibility-of-the-scala-213-standard-library.md b/_sips/sips/drop-forwards-binary-compatibility-of-the-scala-213-standard-library.md
deleted file mode 100644
index 893e9c177b..0000000000
--- a/_sips/sips/drop-forwards-binary-compatibility-of-the-scala-213-standard-library.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: SIP-51 - Drop Forwards Binary Compatibility of the Scala 2.13 Standard Library
-status: under-review
-pull-request-number: 54
-stage: implementation
-
----
diff --git a/_sips/sips/drop-stdlib-forwards-bin-compat.md b/_sips/sips/drop-stdlib-forwards-bin-compat.md
new file mode 100644
index 0000000000..fc17c0df58
--- /dev/null
+++ b/_sips/sips/drop-stdlib-forwards-bin-compat.md
@@ -0,0 +1,315 @@
+---
+layout: sip
+permalink: /sips/:title.html
+stage: implementation
+status: under-review
+title: SIP-51 - Drop Forwards Binary Compatibility of the Scala 2.13 Standard Library
+---
+
+**By: Lukas Rytz**
+
+
+## History
+
+| Date | Version |
+|----------------|--------------------|
+| Dec 8, 2022 | Initial Version |
+
+
+## Summary
+
+I propose to drop the forwards binary compatibility requirement that build tools enforce on the Scala 2.13 standard library.
+This will allow implementing performance optimizations of collection operations that are currently not possible.
+It also unblocks adding new classes and new members to existing classes in the standard library.
+
+
+## Backwards and Forwards Compatibility
+
+A library is backwards binary compatible if code compiled against an old version works with a newer version on the classpath.
+Forwards binary compatibility requires the opposite: code compiled against a new version of a library needs to work with an older version on the classpath.
+A more in-depth explanation of binary compatibility is available on the [Scala documentation site](https://docs.scala-lang.org/overviews/core/binary-compatibility-of-scala-releases.html).
+
+Scala build tools like sbt automatically update dependencies on the classpath to the latest patch version that any other dependency on the classpath requires.
+For example, with the following definition
+
+~~~ scala
+libraryDependencies ++= List(
+ "com.softwaremill.sttp.client3" %% "core" % "3.8.3", // depends on ws 1.3.10
+ "com.softwaremill.sttp.shared" %% "ws" % "1.2.7", // for demonstration
+)
+~~~
+
+sbt updates the `ws` library to version 1.3.10.
+Running the `evicted` command in sbt displays all dependencies whose version were changed.
+
+This build tool feature allows library authors to only maintain backwards binary compatibility in new versions, they don't need to maintain forwards binary compatibility.
+Backwards binary compatible changes include the addition of new methods in existing classes and the addition of new classes.
+Such additions don't impact existing code that was compiled against an older version, all definitions that were previously present are still there.
+
+### The Standard Library
+
+The Scala standard library is treated specially by sbt and other build tools, its version is always pinned to the `scalaVersion` of the build definition and never updated automatically.
+
+For example, the `"com.softwaremill.sttp.client3" %% "core" % "3.8.3"` library has a dependency on `"org.scala-lang" % "scala-library" % "2.13.10"` in its POM file.
+When a project uses this version of the sttp client in a project with `scalaVersion` 2.13.8, sbt will put the Scala library version 2.13.8 on the classpath.
+
+This means that the standard library is required to remain both backwards and forwards binary compatible.
+The implementation of sttp client 3.8.3 can use any feature available in Scala 2.13.10, and that compiled code needs to work correctly with the Scala 2.13.8 standard library.
+
+The suggested change of this SIP is to drop this special handling of the Scala standard library and therefore lift the forwards binary compatibility requirement.
+
+
+## Motivation
+
+### Adding Overrides for Performance
+
+The forwards binary compatibility constraint regularly prevents adding optimized overrides to collection classes.
+The reason is that the bytecode signature of an overriding method is not necessarily identical to the signature of the overridden method.
+Example:
+
+~~~ scala
+class A { def f: Object = "" }
+class B extends A { override def f: String = "" }
+~~~
+
+The bytecode signature of `B.f` has return type `String`.
+(In order to implement dynamic dispatch at run time (overriding), the compiler generates a "bridge" method `B.f` with return type `Object` which forwards to the other `B.f` method.)
+Adding such an override is not forwards binary compatible, because code compiled against `B` can link to the `B.f` method with return type `String`, which would not exist in the previous version.
+
+It's common that forwards binary compatibility prevents adding optimizing overrides, most recently in [LinkedHashMap](https://github.com/scala/scala/pull/10235#issuecomment-1336781619).
+
+Sometimes, if an optimization is considered important, a type test is added to the existing implementation to achieve the same effect.
+These workarounds could be cleaned up.
+Examples are [`mutable.Map.mapValuesInPlace`](https://github.com/scala/scala/blob/v2.13.10/src/library/scala/collection/mutable/Map.scala#L201-L204), [`IterableOnce.foldLeft`](https://github.com/scala/scala/blob/v2.13.10/src/library/scala/collection/IterableOnce.scala#L669-L670), [`Set.concat`](https://github.com/scala/scala/blob/v2.13.10/src/library/scala/collection/Set.scala#L226-L227), and many more.
+
+### Adding Functionality
+
+Dropping forwards binary compatiblity allows adding new methods to existing classes, as well as adding new classes.
+While this opens a big door in principle, I am certain that stability, consistency and caution will remain core considerations when discussing additions to the standard library.
+However, I believe that allowing to (carefully) evolve the standard library is greatly beneficial for the Scala community.
+
+Examples that came up in the past
+ - various proposals for new operations are here: https://github.com/scala/scala-library-next/issues and https://github.com/scala/scala-library-next/pulls
+ - addition of `ExecutionContext.opportunistic` in 2.13.4, which could not be made public: https://github.com/scala/scala/pull/9270
+ - adding `ByteString`: https://contributors.scala-lang.org/t/adding-akkas-bytestring-as-a-scala-module-and-or-stdlib/5967
+ - new string interpolators: https://github.com/scala/scala/pull/8654
+
+## Alternatives and Related Work
+
+For binary compatible overrides, it was considered to add an annotation that would enforce the existing signature in bytecode.
+However, this approach turned out to be too complex in the context of further overrides and Java compatibility.
+Details are in the [corresponding pull request](https://github.com/scala/scala/pull/9141).
+
+Extensions to the standard library can be implemented in a separate library, and such a library exists since 2020 as [scala-library-next](https://github.com/scala/scala-library-next).
+This library has seen very little adoption so far, and I personally don't think this is likely going (or possible) to change.
+One serious drawback of an external library is that operations on existing classes can only be added as extension methods, which makes them less discoverable and requires adding an import.
+This drawback could potentially be mitigated with improvements in Scala IDEs.
+
+An alternative to `scala-library-next` would be to use the Scala 3 library (`"org.scala-lang" % "scala3-library_3"`) which is published with Scala 3 releases.
+This library is handled by build tools like any other library and therefore open for backwards binary compatible additions.
+Until now, the Scala 3 library is exclusively used as a "runtime" library for Scala 3, i.e., it contanis definitions that are required for running code compiled with Scala 3.
+Additions to the Scala 3 library would not be available to the still very large userbase of Scala 2.13.
+Like for `scala-library-next`, additions to existing classes can again only be done in the form of extension methods.
+Also, I believe that there is great value in keeping the Scala 2.13 and 3 standard libraries aligned for now.
+
+
+## Implications
+
+### Possible Linkage Errors
+
+The policy change can only be implemented in new build tool releases, which makes it possible that projects run into linkage errors at run time.
+Concretely, a project might update one of its dependencies to a new version which requires a more recent Scala library than the one defined in the project's `scalaVersion`.
+If the project continues using an old version of sbt, the build tool will keep the Scala library pinned.
+The new library might reference definitions that don't exist in the older Scala library, leading to linkage errors.
+
+### Scala.js and Scala Native
+
+Scala.js distributes a JavaScript version of the Scala library.
+This artifact is currently released once per Scala.js version.
+When a new Scala version comes out, a new Scala.js compiler is released, but the Scala library artifact continues to be used until the next Scala.js version.
+This scheme does not work if the new Scala version has new definitions, so it needs to be adjusted.
+Finding a solution for this problem is necessary and part of the implementation phase.
+
+A similar situation might exist for Scala Native.
+
+### Compiler and Library Version Mismatch
+
+Defining the `scalaVersion` in a project would no longer pin the standard library to that exact version.
+The Scala compiler on the other hand would be kept at the specified version.
+This means that Scala compilers will need to be able to run with a newer version of the Scala library, e.g., the Scala compiler 2.13.10 needs to be able to run with a 2.13.11 standard library on the compilation classpath.
+I think this will not cause any issues.
+
+Note that there are two classpaths at play here: the runtime classpath of the JVM that is running the Scala compiler, and the compilation classpath in which the compiler looks up symbols that are referenced in the source code being compiled.
+The Scala library on the JVM classpath could remain in sync with the compiler version.
+The Scala library on the compilation classpath would be updated by the build tool according to the dependency graph.
+
+### Newer than Expected Library
+
+Because the build tool can update the Scala library version, a project might accidentally use / link to new API that does not yet exist in the `scalaVersion` that is defined in the build definition.
+This is safe, as the project's POM file will have a dependency on the newer version of the Scala library.
+The same situation can appear with any other dependency of a project.
+
+### Applications with Plugin Systems
+
+In applications where plugins are dynamically loaded, plugins compiled with a new Scala library could fail to work correctly if the application is running with an older Scala library.
+
+This is however not a new issue, the proposed change would just extend the existing problem to the Scala library.
+
+## Limitations
+
+Adding new methods or fields to existing traits remains a binary incompatible change.
+This is unrelated to the Standard library, the same is true for other libraries.
+[MiMa](https://github.com/lightbend/mima) is a tool for ensuring changes are binary compatible.
+
+
+## Build Tools
+
+### Mill
+
+In my testing, Mill has the same behavior as sbt, the Scala library version is pinned to the project's `scalaVersion`.
+
+
+
+~~~
+$> cat build.sc
+import mill._, scalalib._
+object proj extends ScalaModule {
+ def scalaVersion = "2.13.8"
+ def ivyDeps = Agg(
+ ivy"com.softwaremill.sttp.client3::core:3.8.3",
+ ivy"com.softwaremill.sttp.shared::ws:1.2.7",
+ )
+}
+$> mill show proj.runClasspath
+[1/1] show > [37/37] proj.runClasspath
+[
+ "qref:868554b6:/Users/luc/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/com/softwaremill/sttp/client3/core_2.13/3.8.3/core_2.13-3.8.3.jar",
+ "qref:f3ba6af6:/Users/luc/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/com/softwaremill/sttp/shared/ws_2.13/1.3.10/ws_2.13-1.3.10.jar",
+ "qref:438104da:/Users/luc/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.8/scala-library-2.13.8.jar",
+ "qref:0c9ef1ab:/Users/luc/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/com/softwaremill/sttp/model/core_2.13/1.5.2/core_2.13-1.5.2.jar",
+ "qref:9b3d3f7d:/Users/luc/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/com/softwaremill/sttp/shared/core_2.13/1.3.10/core_2.13-1.3.10.jar"
+]
+~~~
+
+
+
+### Gradle
+
+Gradle handles the Scala library the same as other dependencies, so it already implements the behavior proposed by this SIP.
+
+
+
+~~~
+$> cat build.gradle
+plugins {
+ id 'scala'
+}
+repositories {
+ mavenCentral()
+}
+dependencies {
+ implementation 'org.scala-lang:scala-library:2.13.8'
+ implementation 'com.softwaremill.sttp.client3:core_2.13:3.8.3'
+ implementation 'com.softwaremill.sttp.shared:ws_2.13:1.2.7'
+}
+$> gradle dependencies --configuration runtimeClasspath
+
+> Task :dependencies
+
+------------------------------------------------------------
+Root project 'proj'
+------------------------------------------------------------
+
+runtimeClasspath - Runtime classpath of source set 'main'.
++--- org.scala-lang:scala-library:2.13.8 -> 2.13.10
++--- com.softwaremill.sttp.client3:core_2.13:3.8.3
+| +--- org.scala-lang:scala-library:2.13.10
+| +--- com.softwaremill.sttp.model:core_2.13:1.5.2
+| | \--- org.scala-lang:scala-library:2.13.8 -> 2.13.10
+| +--- com.softwaremill.sttp.shared:core_2.13:1.3.10
+| | \--- org.scala-lang:scala-library:2.13.9 -> 2.13.10
+| \--- com.softwaremill.sttp.shared:ws_2.13:1.3.10
+| +--- org.scala-lang:scala-library:2.13.9 -> 2.13.10
+| +--- com.softwaremill.sttp.shared:core_2.13:1.3.10 (*)
+| \--- com.softwaremill.sttp.model:core_2.13:1.5.2 (*)
+\--- com.softwaremill.sttp.shared:ws_2.13:1.2.7 -> 1.3.10 (*)
+
+(*) - dependencies omitted (listed previously)
+~~~
+
+
+
+### Maven
+
+Maven [does not update](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html) versions of dependencies that are explicitly listed in the `pom.xml` file, so it's possible to run into linkage errors at run time already now.
+The maven versions plugin can display and update dependencies to newer versions.
+
+
+
+~~~
+$> cat pom.xml
+
+
+ 4.0.0
+ a.b
+ proj
+ 1.0.0-SNAPSHOT
+
+ UTF-8
+ UTF-8
+ 1.8
+ 2.13.8
+
+
+
+ org.scala-lang
+ scala-library
+ ${scala.version}
+
+
+ com.softwaremill.sttp.client3
+ core_2.13
+ 3.8.3
+
+
+ com.softwaremill.sttp.shared
+ ws_2.13
+ 1.2.7
+
+
+
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ 4.8.0
+
+
+
+
+$> mvn dependency:build-classpath
+[INFO] --- maven-dependency-plugin:2.8:build-classpath (default-cli) @ proj ---
+[INFO] Dependencies classpath:
+/Users/luc/.m2/repository/org/scala-lang/scala-library/2.13.8/scala-library-2.13.8.jar:/Users/luc/.m2/repository/com/softwaremill/sttp/client3/core_2.13/3.8.3/core_2.13-3.8.3.jar:/Users/luc/.m2/repository/com/softwaremill/sttp/model/core_2.13/1.5.2/core_2.13-1.5.2.jar:/Users/luc/.m2/repository/com/softwaremill/sttp/shared/core_2.13/1.3.10/core_2.13-1.3.10.jar:/Users/luc/.m2/repository/com/softwaremill/sttp/shared/ws_2.13/1.2.7/ws_2.13-1.2.7.jar
+$> mvn versions:display-dependency-updates
+[INFO] --- versions-maven-plugin:2.13.0:display-dependency-updates (default-cli) @ proj ---
+[INFO] The following dependencies in Dependencies have newer versions:
+[INFO] com.softwaremill.sttp.client3:core_2.13 ............... 3.8.3 -> 3.8.5
+[INFO] com.softwaremill.sttp.shared:ws_2.13 ................. 1.2.7 -> 1.3.12
+[INFO] org.scala-lang:scala-library ....................... 2.13.8 -> 2.13.10
+~~~
+
+
+
+### Bazel
+
+I have never used bazel and did not manage set up / find a sample build definition to test its behavior.
+Help from someone knowing bazel would be appreciated.
+
+### Pants
+
+As with bazel, I did not yet manage to set up / find an example project.
+
+### Other Tools
+
+The SIP might also require changes in other tools such as scala-cli, coursier or bloop.
diff --git a/_sips/sips/multi-source-extension-overloads.md b/_sips/sips/multi-source-extension-overloads.md
index b8a1af4aa8..9812b8bbe3 100644
--- a/_sips/sips/multi-source-extension-overloads.md
+++ b/_sips/sips/multi-source-extension-overloads.md
@@ -1,7 +1,233 @@
---
-title: SIP-54 - Multi-Source Extension Overloads.
+layout: sip
+permalink: /sips/:title.html
+stage: implementation
status: under-review
-pull-request-number: 60
-stage: design
+title: SIP-54 - Multi-Source Extension Overloads
+---
+
+**By: Sébastien Doeraene and Martin Odersky**
+
+## History
+
+| Date | Version |
+|---------------|--------------------|
+| Mar 10th 2023 | Initial Draft |
+
+## Summary
+
+We propose to allow overload resolution of `extension` methods with the same name but imported from several sources.
+For example, given the following definitions:
+
+```scala
+class Foo
+class Bar
+
+object A:
+ extension (foo: Foo) def meth(): Foo = foo
+ def normalMeth(foo: Foo): Foo = foo
+
+object B:
+ extension (bar: Bar) def meth(): Bar = bar
+ def normalMeth(bar: Bar): Bar = bar
+```
+
+and the following use site:
+
+```scala
+import A.*
+import B.*
+
+val foo: Foo = ???
+foo.meth() // works with this SIP; "ambiguous import" without it
+
+// unchanged:
+meth(foo)() // always ambiguous, just like
+normalMeth(foo) // always ambiguous
+```
+
+## Motivation
+
+Extension methods are a great, straightforward way to extend external classes with additional methods.
+One classical example is to add a `/` operation to `Path`:
+
+```scala
+import java.nio.file.*
+
+object PathExtensions:
+ extension (path: Path)
+ def /(child: String): Path = path.resolve(child).nn
+
+def app1(): Unit =
+ import PathExtensions.*
+ val projectDir = Paths.get(".") / "project"
+```
+
+However, as currently specified, they do not compose, and effectively live in a single flat namespace.
+This is understandable from the spec--the *mechanism**, which says that they are just regular methods, but is problematic from an intuitive point of view--the *intent*.
+
+For example, if we also use another extension that provides `/` for `URI`s, we can use it in a separate scope as follows:
+
+```scala
+import java.net.URI
+
+object URIExtensions:
+ extension (uri: URI)
+ def /(child: String): URI = uri.resolve(child)
+
+def app2(): Unit =
+ import URIExtensions.*
+ val rootURI = new URI("https://www.example.com/")
+ val projectURI = rootURI / "project/"
+```
+
+The above does not work anymore if we need to use *both* extensions in the same scope.
+The code below does not compile:
+
+```scala
+def app(): Unit =
+ import PathExtensions.*
+ import URIExtensions.*
+
+ val projectDir = Paths.get(".") / "project"
+ val rootURI = new URI("https://www.example.com/")
+ val projectURI = rootURI / "project/"
+ println(s"$projectDir -> $projectURI")
+end app
+```
+
+*Both* attempts to use `/` result in error messages of the form
+
+```
+Reference to / is ambiguous,
+it is both imported by import PathExtensions._
+and imported subsequently by import URIExtensions._
+```
+
+### Workarounds
+
+The only workarounds that exist are unsatisfactory.
+
+We can avoid using extensions with the same name in the same scope.
+In the above example, that would be annoying enough to defeat the purpose of the extensions in the first place.
+
+Another possibility is to *define* all extension methods of the same name in the same `object` (or as top-level definitions in the same file).
+This is possible, although cumbersome, if they all come from the same library.
+However, it is impossible to combine extension methods coming from separate libraries in this way.
+
+Finally, there exists a trick with `given`s of empty refinements:
+
+```scala
+object PathExtensions:
+ given pathExtensions: {} with
+ extension (path: Path)
+ def /(child: String): Path = path.resolve(child).nn
+
+object URIExtensions:
+ given uriExtensions: {} with
+ extension (uri: URI)
+ def /(child: String): URI = uri.resolve(child)
+```
+
+The empty refinement `: {}` prevents those `given`s from polluting the actual implicit scope.
+`extension`s defined inside `given`s that are in scope can be used, so this trick allows to use `/` with the imports of `PathExtensions.*` and `URIExtensions.*`.
+The `given`s must still have different names for the trick to work.
+This workaround is however quite obscure.
+It hides intent behind a layer of magic (and an additional indirection at run-time).
+
+### Problem for migrating off of implicit classes
+
+Scala 2 implicit classes did not suffer from the above issues, because they were disambiguated by the name of the implicit class (not the name of the method).
+This means that there are libraries that cannot migrate off of implicit classes to use `extension` methods without significantly degrading their usability.
+
+## Proposed solution
+
+We propose to relax the resolution of extension methods, so that they can be resolved from multiple imported sources.
+Instead of rejecting the `/` call outright because of ambiguous imports, the compiler should try the resolution from all the imports, and keep the only one (if any) for which the receiver type matches.
+
+Practically speaking, this means that the above `app()` example would compile and behave as expected.
+
+### Non-goals
+
+It is *not* a goal of this proposal to allow resolution of arbitrary overloads of regular methods coming from multiple imports.
+Only `extension` method calls are concerned by this proposal.
+The complexity budget of relaxing *all* overloads in this way is deemed too high, whereas it is acceptable for `extension` method calls.
+
+For the same reason, we do not propose to change regular calls of methods that happen to be `extension` methods.
+
+### Specification
+
+We make two changes to the [specification of extension methods](https://docs.scala-lang.org/scala3/reference/contextual/extension-methods.html).
+
+In the section [Translation of Extension Methods](https://docs.scala-lang.org/scala3/reference/contextual/extension-methods.html#translation-of-extension-methods), we make it clearer that the "desugared" version of the call site may require an explicit qualifier.
+This is not strictly a novelty of this SIP, since it could already happen with `given`s and implicit scopes, but this SIP adds one more case where this can happen.
+
+Previously:
+
+> So, the definition of circumference above translates to the following method, and can also be invoked as such:
+>
+> ` def circumference(c: Circle): Double = c.radius * math.Pi * 2`
+>
+> `assert(circle.circumference == circumference(circle))`
+
+With this SIP:
+
+> So, the definition of circumference above translates to the following method, and can also be invoked as such:
+>
+> ` def circumference(c: Circle): Double = c.radius * math.Pi * 2`
+>
+> `assert(circle.circumference == circumference(circle))`
+>
+> or
+>
+> `assert(circle.circumference == qualifierPath.circumference(circle))`
+>
+> for some `qualifierPath` in which `circumference` is actually declared.
+> Explicit qualifiers may be required when the extension method is resolved through `given` instances, implicit scopes, or disambiguated from several imports.
---
+
+In the section [Translation of Calls to Extension Methods](https://docs.scala-lang.org/scala3/reference/contextual/extension-methods.html#translation-of-calls-to-extension-methods), we amend step 1. of "The precise rules for resolving a selection to an extension method are as follows."
+
+Previously:
+
+> Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional, and where `T` is the expected type.
+> The following two rewritings are tried in order:
+>
+> 1. The selection is rewritten to `m[Ts](e)`.
+
+With this SIP:
+
+> 1. The selection is rewritten to `m[Ts](e)` and typechecked, using the following slight modification of the name resolution rules:
+>
+> - If `m` is imported by several imports which are all on the same nesting level, try each import as an extension method instead of failing with an ambiguity.
+> If only one import leads to an expansion that typechecks without errors, pick that expansion.
+> If there are several such imports, but only one import which is not a wildcard import, pick the expansion from that import.
+> Otherwise, report an ambiguous reference error.
+
+### Compatibility
+
+The proposal only alters situations where the previous specification would reject the program with an ambiguous import.
+Therefore, we expect it to be backward source compatible.
+
+The resolved calls could previously be spelled out by hand (with fully-qualified names), so binary compatibility and TASTy compatibility are not affected.
+
+### Other concerns
+
+With this SIP, some calls that would be reported as *ambiguous* in their "normal" form can actually be written without ambiguity if used as extensions.
+That may be confusing to some users.
+Although specific error messages are not specified and therefore outside the SIP scope, we encourage the compiler implementation to enhance the "ambiguous" error message to address this confusion.
+If some or all of the involved ambiguous targets are `extension` methods, the compiler should point out that the call might be resolved unambiguously if used as an extension.
+
+## Alternatives
+
+A number of alternatives were mentioned in [the Contributors thread](https://contributors.scala-lang.org/t/change-shadowing-mechanism-of-extension-methods-for-on-par-implicit-class-behavior/5831), but none that passed the bar of "we think this is actually implementable".
+
+## Related work
+
+- [Contributors thread acting as de facto Pre-SIP](https://contributors.scala-lang.org/t/change-shadowing-mechanism-of-extension-methods-for-on-par-implicit-class-behavior/5831)
+- [Pull Request in dotty](https://github.com/lampepfl/dotty/pull/17050) to support it under an experimental import
+
+## FAQ
+
+This section will probably initially be empty. As discussions on the proposal progress, it is likely that some questions will come repeatedly. They should be listed here, with appropriate answers.
diff --git a/_sips/sips/quote-pattern-explicit-type-variable-syntax.md b/_sips/sips/quote-pattern-explicit-type-variable-syntax.md
deleted file mode 100644
index 9b2f0dcb9d..0000000000
--- a/_sips/sips/quote-pattern-explicit-type-variable-syntax.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: SIP-53 - Quote Pattern Explicit Type Variable Syntax
-status: under-review
-pull-request-number: 59
-stage: design
-
----
diff --git a/_sips/sips/quote-pattern-type-variable-syntax.md b/_sips/sips/quote-pattern-type-variable-syntax.md
new file mode 100644
index 0000000000..a0aed079b9
--- /dev/null
+++ b/_sips/sips/quote-pattern-type-variable-syntax.md
@@ -0,0 +1,141 @@
+---
+layout: sip
+permalink: /sips/:title.html
+stage: implementation
+status: waiting-for-implementation
+title: SIP-53 - Quote pattern explicit type variable syntax
+---
+
+**By: Nicolas Stucki**
+
+## History
+
+| Date | Version |
+|---------------|--------------------|
+| Feb 28th 2022 | Initial Draft |
+
+## Summary
+
+This SIP proposes additions to the syntax of type variable definitions to bring quoted type matching to par with quoted expression matching.
+Specifically, the ability to declare type variables explicitly and define them with bounds.
+
+It also proposes some enhancements to make the use of type variables simpler.
+The idea is to reduce the number of cases where we need to write backticks around type variable name references.
+Namely when using explicit type variable definitions.
+
+## Motivation
+
+### Background
+
+* [Reference Documentation](http://dotty.epfl.ch/docs/reference/metaprogramming/macros.html#type-variables)
+
+Quoted expressions support two ways of defining type variables: explicit and nested.
+
+##### Explicit type variables
+The initial `type` declarations in the pattern with a type variable name (lowercase names as with normal pattern type variables) are type variable definitions. Type variable references need to be in backticks. Otherwise, we assume they are nested type variables and emit an error. These definitions can have bounds defined on them.
+```scala
+case '{ type t; $x: `t` } => f[t](x: Expr[t])
+case '{ type u; ($ls: List[`u`]).map($f: `u` => Int) } => g[u](ls: Expr[List[u]], f: Expr[u => Int])
+case '{ type tail <: Tuple; $x: *:[Int, `tail`] } => h[tail](x: Expr[*:[Int, tail])
+```
+
+##### Nested type variable
+Types with a type variable name introduce a new type variable. These cannot be references with a backticked reference due to their scope. We cannot add explicit bounds to them, but in certain cases we can infer their some bounds. These variables become explicit type variables in the internal representation after typing.
+```scala
+case '{ $x: t } => f[t](x: Expr[t])
+```
+
+
+##### Type Patterns
+Quoted type patterns only support nested type variable definitions. Explicit type variables are not supported in the source due to an oversight. These variables become explicit type variables in the internal representation after typing. The bounds of the type variable are `Any` and `Nothing`.
+```scala
+case '[ t ] => f[t]
+case '[ List[t] ] => g[t]
+```
+
+### Support type bounds in quoted type patterns
+
+We want to be able to set the bounds of type variables to be able to match against type constructors that have type bounds. For example, the tuple `*:` type.
+```scala
+case '[ head *: tail ] => h[tail]
+```
+See https://github.com/lampepfl/dotty/issues/11738.
+
+### Support matching on any kind of type
+We want to match against higher-kinded (or `AnyKind`) types. This is not possible due to the default upper bound of `Any`.
+See https://github.com/lampepfl/dotty/issues/10864.
+
+### Support multiple references to the same type in quoted type patterns
+We want to be able to match using several references to the same type variable.
+```scala
+case '[ (t, t, t) ] => f[t] // t is going to match the glb of the tuple T1, T2, T3
+```
+
+### Simplify the use of explicit type variables
+It is inconsistent to need to use backticks for references to explicit type variables in the quote but not outside.
+We want to be able to refer to the variable by its non-backticked name uniformly.
+```diff
+- case '{ type u; ($ls: List[`u`]).map($f: `u` => Int) } => g[u](ls: Expr[List[u]], f: Expr[u => Int])
++ case '{ type u; ($ls: List[u]).map($f: u => Int) } => g[u](ls: Expr[List[u]], f: Expr[u => Int])
+```
+
+## Proposed solution
+
+### High-level overview
+
+We first want to introduce syntax for explicit type variable definitions in quoted type patterns that aligns with expression quoted patterns. We can use the syntax described in [explicit type variables](#explicit-type-variables).
+
+```scala
+case '[ type t; List[`t`] ] => f[t]
+case '[ type tail <: Tuple; *:[Int, `tail`] ] => g[tail]
+```
+
+Second, we want the remove the need for backticks for references to explicit type variable definitions. If we have an explicit type variable definition and a type variable with the same name, we can syntactically assume these are the same and not introduce a new nested type variable.
+```scala
+case '{ type t; $x: t } => f[t](x: Expr[t])
+case '{ type u; ($ls: List[u]).map($f: u => Int) } => g[u](ls: Expr[List[u]], f: Expr[u => Int])
+case '{ type tail <: Tuple; $x: *:[Int, tail] } => h[tail](x: Expr[*:[Int, tail])
+```
+```scala
+case '[ type t; List[t] ] => f[t]
+case '[ type tail <: Tuple; *:[Int, tail] ] => g[tail]
+```
+
+### Specification
+
+Adding the explicit type variable definition to quoted type patterns is relatively straightforward as nested type variables become explicit ones internally. We would only need to update the parser to accept a new kind of type in the syntax. This type would only be used in the quote type pattern syntax, which is self-contained in the language's grammar.
+
+```diff
+Quoted ::= ‘'’ ‘{’ Block ‘}’
+- | ‘'’ ‘[’ Type ‘]’
++ | ‘'’ ‘[’ TypeBlock ‘]’
++TypeBlock ::= {TypeBlockStat semi} Type
++TypeBlockStat ::= ‘type’ {nl} TypeDcl
+```
+
+Allowing non-backticked references to explicit type variable definitions would not create any conflict, as these would currently cause a double definition error. The grammar would not need to change. This would only interact with the process of typing quoted patterns.
+
+### Compatibility
+
+There are no compatibility issues because the parser or typer rejected all these cases.
+
+TASTy only contains explicit type variable definitions, and this encoding would not change. Note that TASTy supports _type blocks_ using the regular `Block` AST. These contain type declaration in their statements and a type instead of the expression.
+
+### Other concerns
+
+* Tools that parse Scala code must be updated with this new grammar.
+* Tools that use TASTy would not be affected.
+
+
+
+## Alternatives
+
+* We could find a different syntax for explicit type variables in quoted type patterns. The drawback is that we need to specify and explain a secondary syntax.
+* Don't include the backticks improvements.
+
+## Related work
+
+* Proof of concept of type variable syntax: https://github.com/lampepfl/dotty/pull/16910
+* Proof of concept of backticks (only interested in the first bullet point): https://github.com/lampepfl/dotty/pull/16935
+
+
diff --git a/_tour/basics.md b/_tour/basics.md
index 540cd9e4a6..f7cb96c667 100644
--- a/_tour/basics.md
+++ b/_tour/basics.md
@@ -532,4 +532,4 @@ In Scala 3, with the `@main` annotation, a main method is automatically generate
## More resources
-* [Scala book](/overviews/scala-book/prelude-taste-of-scala.html) overview
+* [Scala book](/scala3/book/taste-intro.html) overview
diff --git a/_tour/case-classes.md b/_tour/case-classes.md
index 2386fb0ead..17aa73fc7c 100644
--- a/_tour/case-classes.md
+++ b/_tour/case-classes.md
@@ -28,7 +28,7 @@ val frankenstein = Book("978-0486282114")
{% endtabs %}
-Notice how the keyword `new` was not used to instantiate the `Book` case class. This is because case classes have an `apply` method by default which takes care of object construction.
+Although that is usually left out, it is possible to explicitly use the `new` keyword, as `new Book()`. This is because case classes have an `apply` method by default which takes care of object construction.
When you create a case class with parameters, the parameters are public `val`s.
diff --git a/_tour/classes.md b/_tour/classes.md
index be64ca7284..683ae049cf 100644
--- a/_tour/classes.md
+++ b/_tour/classes.md
@@ -262,5 +262,5 @@ point.x // <-- does not compile
## More resources
-* Learn more about Classes in the [Scala Book](/overviews/scala-book/classes.html)
-* How to use [Auxiliary Class Constructors](/overviews/scala-book/classes-aux-constructors.html)
+* Learn more about Classes in the [Scala Book](/scala3/book/domain-modeling-tools.html#classes)
+* How to use [Auxiliary Class Constructors](/scala3/book/domain-modeling-tools.html#auxiliary-constructors)
diff --git a/_tour/implicit-conversions.md b/_tour/implicit-conversions.md
index cf93a062f2..7c0b5840e4 100644
--- a/_tour/implicit-conversions.md
+++ b/_tour/implicit-conversions.md
@@ -38,95 +38,6 @@ In the second case, a conversion `c` is searched for, which is applicable to `e`
An example is to compare two strings `"foo" < "bar"`. In this case, `String` has no member `<`, so the implicit conversion `Predef.augmentString("foo") < "bar"` is inserted. (`scala.Predef` is automatically imported into all Scala programs.)
-### How are implicit conversions brought into scope? ###
-
-{% tabs implicit-conversion-scope class=tabs-scala-version %}
-{% tab 'Scala 2' %}
-In Scala 2, an implicit conversion is brought into scope by importing from the object that defined it, (e.g. `Conversions` in this case). If the implicit conversion is in the companion object of the argument type, (e.g. `Student` in this case), then no import is necessary.
-
-```scala
-import scala.language.implicitConversions // required to define an implicit conversion
-
-case class Student(name: String)
-object Student {
- implicit def fromStudentToInt(student: Student): Int = student.name.length
-}
-
-object Conversions {
- implicit def fromStringToStudent(name: String): Student = Student(name)
-}
-
-import Conversions._
-object Usage {
- def main(args: Array[String]) = {
- val reginald: Student = "Reginald" // applies the conversion Conversions.fromStringToStudent
- println(reginald + 2) // applies the conversion Student.fromStudentToInt
- }
-}
-```
-{% endtab %}
-{% tab 'Scala 3' %}
-In Scala 3, an implicit conversion is brought into scope by either importing `given` or the named conversion from the object that defined it, (e.g. `Conversions` in this case).
-
-Note that as of Scala 3, implicit conversions cannot be brought into scope anymore by means of a wildcard import (`*`).
-
-Given the example:
-
-```scala
-case class Student(name: String):
- def printName: Unit = println(name)
-object Student:
- given Conversion[Student, Int] = _.name.length
-
-object Conversions:
- given fromStringToStudent: Conversion[String, Student] = Student(_)
-```
-
-The following imports would bring the `Conversion[String, Student]` into scope:
- - `import Conversions.given`
- - `import Conversions.{given Conversion[String, Student]}`
- - `import Conversions.fromStringToStudent`
-
-If the implicit conversion is in the companion object of the argument type, (e.g. `Student` in this case), then no import is necessary.
-
-```scala
-import Conversions.given
-object Usage:
- @main def run =
- val reginald: Student = "Reginald" // applies the Conversion[String, Student]
- println(reginald + 2) // applies the Conversion[Student, Int]
-```
-{% endtab %}
-{% endtabs %}
-
-Further reading:
- - [Where does Scala look for implicits? (on StackOverflow)](https://docs.scala-lang.org/tutorials/FAQ/index.html#where-does-scala-look-for-implicits).
-
-### Beware the power of implicit conversions
-
-{% tabs implicit-conversion-warning class=tabs-scala-version %}
-{% tab 'Scala 2' %}
-Because implicit conversions can have pitfalls if used indiscriminately the compiler warns when compiling the implicit conversion definition.
-
-To turn off the warnings take either of these actions:
-
-* Import `scala.language.implicitConversions` into the scope of the implicit conversion definition
-* Invoke the compiler with `-language:implicitConversions`
-
-No warning is emitted when the conversion is applied by the compiler.
-{% endtab %}
-{% tab 'Scala 3' %}
-Because implicit conversions can have pitfalls if used indiscriminately the compiler warns in two situations:
-- when compiling a Scala 2 style implicit conversion definition.
-- at the call site where a given instance of `scala.Conversion` is inserted as a conversion.
-
-To turn off the warnings take either of these actions:
-
-- Import `scala.language.implicitConversions` into the scope of:
- - a Scala 2 style implicit conversion definition
- - call sites where a given instance of `scala.Conversion` is inserted as a conversion.
-- Invoke the compiler with `-language:implicitConversions`
-{% endtab %}
-{% endtabs %}
+Further reading: [Implicit Conversions (in the Scala book)]({% link _overviews/scala3-book/ca-implicit-conversions.md %}).
[exts]: {% link _overviews/scala3-book/ca-extension-methods.md %}
diff --git a/_tour/package-objects.md b/_tour/package-objects.md
index c13ff9115c..52484564a9 100644
--- a/_tour/package-objects.md
+++ b/_tour/package-objects.md
@@ -7,7 +7,7 @@ num: 36
previous-page: packages-and-imports
---
-Often, it is convenient to have definitions accessible accross an entire package, and not need to invent a
+Often, it is convenient to have definitions accessible across an entire package, and not need to invent a
name for a wrapper `object` to contain them.
{% tabs pkg-obj-vs-top-lvl_1 class=tabs-scala-version %}
diff --git a/_tour/traits.md b/_tour/traits.md
index cf12b763be..3ead3d3974 100644
--- a/_tour/traits.md
+++ b/_tour/traits.md
@@ -161,5 +161,5 @@ The `trait Pet` has an abstract field `name` that gets implemented by Cat and Do
## More resources
-* Learn more about traits in the [Scala Book](/overviews/scala-book/traits-intro.html)
-* Use traits to define [Enum](/overviews/scala-book/enumerations-pizza-class.html)
+* Learn more about traits in the [Scala Book](/scala3/book/domain-modeling-tools.html#traits)
+* Use traits to define [Enum](/scala3/book/domain-modeling-fp.html#modeling-the-data)
diff --git a/_zh-cn/overviews/core/futures.md b/_zh-cn/overviews/core/futures.md
index c37b66efe5..97f64ed7aa 100644
--- a/_zh-cn/overviews/core/futures.md
+++ b/_zh-cn/overviews/core/futures.md
@@ -11,491 +11,1254 @@ language: zh-cn
## 简介
-Future提供了一套高效便捷的非阻塞并行操作管理方案。其基本思想很简单,所谓Future,指的是一类占位符对象,用于指代某些尚未完成的计算的结果。一般来说,由Future指代的计算都是并行执行的,计算完毕后可另行获取相关计算结果。以这种方式组织并行任务,便可以写出高效、异步、非阻塞的并行代码。
+Future提供了一套高效便捷的非阻塞并行操作管理方案。其基本思想很简单,所谓 [`Future`](https://www.scala-lang.org/api/current/scala/concurrent/Future.html),指的是一类占位符对象,用于指代某些尚未完成的计算的结果。一般来说,由Future指代的计算都是并行执行的,计算完毕后可另行获取相关计算结果。以这种方式组织并行任务,便可以写出高效、异步、非阻塞的并行代码。
-默认情况下,future和promise并不采用一般的阻塞操作,而是依赖回调进行非阻塞操作。为了在语法和概念层面更加简明扼要地使用这些回调,Scala还提供了flatMap、foreach和filter等算子,使得我们能够以非阻塞的方式对future进行组合。当然,future仍然支持阻塞操作——必要时,可以阻塞等待future(不过并不鼓励这样做)。
+默认情况下,future和promise并不采用一般的阻塞操作,而是依赖回调进行非阻塞操作。为了在语法和概念层面更加简明扼要地使用这些回调,Scala还提供了 `flatMap`、`foreach` 和 `filter` 等算子,使得我们能够以非阻塞的方式对future进行组合。
+当然,future仍然支持阻塞操作——必要时,可以阻塞等待future(不过并不鼓励这样做)。
+
+
+
+典型的 future 像这样:
+
+{% tabs futures-00 %}
+{% tab 'Scala 2 and 3' for=futures-00 %}
+
+```scala
+val inverseFuture: Future[Matrix] = Future {
+ fatMatrix.inverse() // non-blocking long lasting computation
+}(executionContext)
+```
+
+{% endtab %}
+{% endtabs %}
+
+或者更习惯的用法:
+
+{% tabs futures-01 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-01 %}
+
+```scala
+implicit val ec: ExecutionContext = ...
+val inverseFuture : Future[Matrix] = Future {
+ fatMatrix.inverse()
+} // ec is implicitly passed
+```
+
+{% endtab %}
+
+{% tab 'Scala 3' for=futures-01 %}
+
+```scala
+given ExecutionContext = ...
+val inverseFuture : Future[Matrix] = Future {
+ fatMatrix.inverse()
+} // execution context is implicitly passed
+```
+
+{% endtab %}
+{% endtabs %}
+
+这两个代码片段都将 `fatMatrix.inverse()` 的执行委托给 `ExecutionContext`,并在 `inverseFuture` 中体现计算结果。
+
+## 执行上下文
+
+Future 和 Promises 围绕 [`ExecutionContext`s](https://www.scala-lang.org/api/current/scala/concurrent/ExecutionContext.html) 展开,负责执行计算。
+
+`ExecutionContext` 类似于 [Executor](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html):
+它可以在新线程、线程池或当前线程中自由地执行计算(尽管不鼓励在当前线程中执行计算 -- 更多内容见下文)。
+
+`scala.concurrent` 包是开箱即用的,它带有 `ExecutionContext` 实现,一个全局静态线程池。
+它也可以将 `Exector` 转换为 `ExecutionContext`。
+最后,用户可以自由扩展 `ExecutionContext` trait来实现自己的执行上下文,但只有极少数情况下需要这么做。
+
+### 全局执行上下文
+
+`ExecutionContext.global` 是由 [ForkJoinPool](https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html) 支持的 `ExecutionContext`。
+它应该满足大部分情况,但有几点需要注意。
+`ForkJoinPool` 管理有限数量的线程(线程的最大数量由 *parallelism level* 指定)。
+仅当每个阻塞调用都包装在 `blocking` 调用中时(更多内容见下文),并发阻塞计算的数量才能超过并行度级别。
+否则,全局执行上下文中的线程池会有饥饿死锁风险,致使任何计算无法继续进行。
+缺省情况下,`ExecutionContext.global` 将其底层ForkJoin连接池的并行级别设置为可用处理器的数量([Runtime.availableProcessors](https://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#availableProcessors%28%29))。
+通过设置以下一个(或多个) VM 属性,来重载这个设置:
+
+ * scala.concurrent.context.minThreads - 缺省为 `Runtime.availableProcessors`
+ * scala.concurrent.context.numThreads - 可以是一个数字,或者是 “xN” 这样形式中的乘数(N);缺省为 `Runtime.availableProcessors`
+ * scala.concurrent.context.maxThreads - 缺省为 `Runtime.availableProcessors`
+
+只要并行度的数值在 `[minThreads; maxThreads]` 范围内,它就可以给 `numThreads` 赋值。
+
+如上所述,在存在阻塞计算的情况下,`ForkJoinPool` 可以将线程数增加到超过 `parallelismLevel`。
+如 `ForkJoinPool` API 中所述,这只有在明确通知 `ForkJoinPool` 时才有可能:
+
+{% tabs futures-02 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-02 %}
+
+```scala
+import scala.concurrent.{ Future, ExecutionContext }
+import scala.concurrent.forkjoin._
+
+// the following is equivalent to `implicit val ec = ExecutionContext.global`
+import ExecutionContext.Implicits.global
+
+Future {
+ ForkJoinPool.managedBlock(
+ new ManagedBlocker {
+ var done = false
+
+ def block(): Boolean = {
+ try {
+ myLock.lock()
+ // ...
+ } finally {
+ done = true
+ }
+ true
+ }
+
+ def isReleasable: Boolean = done
+ }
+ )
+}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=futures-02 %}
+
+```scala
+import scala.concurrent.{ Future, ExecutionContext }
+import scala.concurrent.forkjoin.*
+
+// the following is equivalent to `given ExecutionContext = ExecutionContext.global`
+import ExecutionContext.Implicits.global
+
+Future {
+ ForkJoinPool.managedBlock(
+ new ManagedBlocker {
+ var done = false
+
+ def block(): Boolean =
+ try
+ myLock.lock()
+ // ...
+ finally
+ done = true
+ true
+
+ def isReleasable: Boolean = done
+ }
+ )
+}
+```
+{% endtab %}
+{% endtabs %}
+
+幸运的是,并发包为这提供了便捷的方法:
+
+{% tabs blocking %}
+{% tab 'Scala 2 and 3' for=blocking %}
+
+```scala
+import scala.concurrent.Future
+import scala.concurrent.blocking
+
+Future {
+ blocking {
+ myLock.lock()
+ // ...
+ }
+}
+```
+
+{% endtab %}
+{% endtabs %}
+
+注意 `blocking` 是一个通用结构,它将会在[下面](#future-内的阻塞)作深入探讨。
+
+最后你必须记住 `ForkJoinPool` 不是设计用来长连接阻塞操作。
+即使收到 `blocking` 通知,池也可能无法像预期的那样生成新工作,而创建新工作线程时,它们的数量也可能多达 32767。
+为了给您有个概念,以下代码将使用 32000 个线程:
+
+{% tabs futures-03 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-03 %}
+
+```scala
+implicit val ec = ExecutionContext.global
+
+for (i <- 1 to 32000) {
+ Future {
+ blocking {
+ Thread.sleep(999999)
+ }
+ }
+}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=futures-03 %}
+
+```scala
+given ExecutionContext = ExecutionContext.global
+
+for i <- 1 to 32000 do
+ Future {
+ blocking {
+ Thread.sleep(999999)
+ }
+ }
+```
+
+{% endtab %}
+{% endtabs %}
+
+如果您需要包装持久的阻塞操作,我们建议使用专用的 `ExecutionContext`,例如通过包装Java 的 `Executor`。
+
+### 适配 Java Executor
+
+使用 `ExecutionContext.fromExecutor` 方法,你可以把 `Executor` 包装进 `ExecutionContext`。
+例如:
+
+{% tabs executor class=tabs-scala-version %}
+{% tab 'Scala 2' for=executor %}
+
+```scala
+ExecutionContext.fromExecutor(new ThreadPoolExecutor( /* your configuration */ ))
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=executor %}
+
+```scala
+ExecutionContext.fromExecutor(ThreadPoolExecutor( /* your configuration */ ))
+```
+
+{% endtab %}
+{% endtabs %}
+
+### 同步执行上下文
+
+也许试图得到一个在当前线程中运行计算的 `ExecutionContext`:
+
+{% tabs bad-example %}
+{% tab 'Scala 2 and 3' for=bad-example %}
+
+```scala
+val currentThreadExecutionContext = ExecutionContext.fromExecutor(
+ new Executor {
+ // Do not do this!
+ def execute(runnable: Runnable) = runnable.run()
+ })
+```
+
+{% endtab %}
+{% endtabs %}
+
+应该避免这种情况,因为它会在执行 future 时引入不确定性。
+
+{% tabs bad-example-2 %}
+{% tab 'Scala 2 and 3' for=bad-example-2 %}
+
+```scala
+Future {
+ doSomething
+}(ExecutionContext.global).map {
+ doSomethingElse
+}(currentThreadExecutionContext)
+```
+
+{% endtab %}
+{% endtabs %}
+
+`doSomethingElse` 调用,可能在 `doSomething` 的线程中执行或者在主线程中执行,这样可以是同步的或者是异步的。
+正如[这里](https://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/)解释的,一个调用不能两者都是。
## Future
所谓Future,是一种用于指代某个尚未就绪的值的对象。而这个值,往往是某个计算过程的结果:
-- 若该计算过程尚未完成,我们就说该Future未就位;
-- 若该计算过程正常结束,或中途抛出异常,我们就说该Future已就位。
+1. 若该计算过程尚未完成,我们就说该Future **未就位**;
+2. 若该计算过程正常结束,或中途抛出异常,我们就说该Future **已就位**。
Future的就位分为两种情况:
-- 当Future带着某个值就位时,我们就说该Future携带计算结果成功就位。
-- 当Future因对应计算过程抛出异常而就绪,我们就说这个Future因该异常而失败。
+1. 当 `Future` 带着某个值就位时,我们就说该 future 携带计算结果**成功就位**。
+2. 当 `Future` 因对应计算过程抛出异常而就绪,我们就说这个 future因该异常而**失败**。
-Future的一个重要属性在于它只能被赋值一次。一旦给定了某个值或某个异常,future对象就变成了不可变对象——无法再被改写。
+`Future` 的一个重要属性在于它只能被赋值一次。一旦给定了某个值或某个异常,`Future` 对象就变成了不可变对象——无法再被改写。
-创建future对象最简单的方法是调用future方法,该future方法启用异步(asynchronous)计算并返回保存有计算结果的futrue,一旦该future对象计算完成,其结果就变的可用。
+创建future对象最简单的方法是调用 `Future.apply` 方法,该future方法启用异步(asynchronous)计算并返回保存有计算结果的futrue,一旦该future对象计算完成,其结果就变的可用。
-注意_Future[T]_ 是表示future对象的类型,而future是方法,该方法创建和调度一个异步计算,并返回随着计算结果而完成的future对象。
+注意 _Future[T]_ 是表示future对象的类型,而 `Future.apply` 是方法,该方法创建和调度一个异步计算,并返回随着计算结果而完成的future对象。
这最好通过一个例子予以说明。
假设我们使用某些流行的社交网络的假定API获取某个用户的朋友列表,我们将打开一个新对话(session),然后发送一个请求来获取某个特定用户的好友列表。
- import scala.concurrent._
- import ExecutionContext.Implicits.global
+{% tabs futures-04 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-04 %}
- val session = socialNetwork.createSessionFor("user", credentials)
- val f: Future[List[Friend]] = Future {
- session.getFriends()
- }
+```scala
+import scala.concurrent._
+import ExecutionContext.Implicits.global
-以上,首先导入scala.concurrent 包使得Future类型和future构造函数可见。我们将马上解释第二个导入。
+val session = socialNetwork.createSessionFor("user", credentials)
+val f: Future[List[Friend]] = Future {
+ session.getFriends()
+}
+```
-然后我们初始化一个session变量来用作向服务器发送请求,用一个假想的 createSessionFor 方法来返回一个List[Friend]。为了获得朋友列表,我们必须通过网络发送一个请求,这个请求可能耗时很长。这能从调用getFriends方法得到解释。为了更好的利用CPU,响应到达前不应该阻塞(block)程序的其他部分执行,于是在计算中使用异步。future方法就是这样做的,它并行地执行指定的计算块,在这个例子中是向服务器发送请求和等待响应。
+{% endtab %}
+{% tab 'Scala 3' for=futures-04 %}
-一旦服务器响应,future f 中的好友列表将变得可用。
+```scala
+import scala.concurrent.*
+import ExecutionContext.Implicits.global
-未成功的尝试可能会导致一个异常(exception)。在下面的例子中,session的值未被正确的初始化,于是在future的计算中将抛出NullPointerException,future f 不会圆满完成,而是以此异常失败。
+val session = socialNetwork.createSessionFor("user", credentials)
+val f: Future[List[Friend]] = Future {
+ session.getFriends()
+}
+```
- val session = null
- val f: Future[List[Friend]] = Future {
- session.getFriends
- }
+{% endtab %}
+{% endtabs %}
-`import ExecutionContext.Implicits.global` 上面的线条导入默认的全局执行上下文(global execution context),执行上下文执行执行提交给他们的任务,也可把执行上下文看作线程池,这对于future方法来说是必不可少的,因为这可以处理异步计算如何及何时被执行。我们可以定义自己的执行上下文,并在future上使用它,但是现在只需要知道你能够通过上面的语句导入默认执行上下文就足够了。
+以上,首先导入 `scala.concurrent` 包使得 `Future` 类型可见。
+我们将马上解释第二个导入。
-我们的例子是基于一个假定的社交网络API,此API的计算包含发送网络请求和等待响应。提供一个涉及到你能试着立即使用的异步计算的例子是公平的。假设你有一个文本文件,你想找出一个特定的关键字第一次出现的位置。当磁盘正在检索此文件内容时,这种计算可能会陷入阻塞,因此并行的执行该操作和程序的其他部分是合理的(make sense)。
+然后我们用一个假想的 `createSessionFor` 方法去初始化一个session变量,该变量用作向服务器发送请求。
+为了获得朋友列表,我们必须通过网络发送一个请求,这个请求可能耗时很长。
+这能从调用 `getFriends` 方法得到解释,该方法返回 `List[Friend]`。
+为了更好的利用CPU,响应到达前不应该阻塞(block)程序的其他部分执行,于是在计算中使用异步。`Future.apply` 方法就是这样做的,它并行地执行指定的计算块,在这个例子中是向服务器发送请求和等待响应。
- val firstOccurrence: Future[Int] = Future {
- val source = scala.io.Source.fromFile("myText.txt")
- source.toSeq.indexOfSlice("myKeyword")
- }
+一旦服务器响应,future `f` 中的好友列表将变得可用。
+
+未成功的尝试可能会导致一个异常(exception)。在下面的例子中,`session` 的值未被正确的初始化,于是在 `Future` 阻塞中的计算将抛出 `NullPointerException`。
+该future `f` 不会圆满完成,而是以此异常失败:
+
+{% tabs futures-04b %}
+{% tab 'Scala 2 and 3' for=futures-04b %}
+
+```scala
+val session = null
+val f: Future[List[Friend]] = Future {
+ session.getFriends
+}
+```
+
+{% endtab %}
+{% endtabs %}
+
+上面 `import ExecutionContext.Implicits.global` 这行,导入默认的全局执行上下文(global execution context)。
+执行上下文执行提交给他们的任务,也可把执行上下文看作线程池。
+这对于 `Future.apply` 方法来说是必不可少的,因为这可以处理异步计算如何及何时被执行。
+可以定义自己的执行上下文,并在 `Future` 上使用它,但是现在只需要知道你能够通过上面的语句导入默认执行上下文就足够了。
+
+我们的例子是基于一个假定的社交网络 API,此 API 的计算包含发送网络请求和等待响应。
+提供一个涉及到你能试着立即使用的异步计算的例子是公平的。假设你有一个文本文件,你想找出一个特定的关键字第一次出现的位置。
+当磁盘正在检索此文件内容时,这种计算可能会陷入阻塞,因此并行的执行该操作和程序的其他部分是合理的(make sense)。
+
+{% tabs futures-04c %}
+{% tab 'Scala 2 and 3' for=futures-04c %}
+
+```scala
+val firstOccurrence: Future[Int] = Future {
+ val source = scala.io.Source.fromFile("myText.txt")
+ source.toSeq.indexOfSlice("myKeyword")
+}
+```
+
+{% endtab %}
+{% endtabs %}
### Callbacks(回调函数)
-现在我们知道如何开始一个异步计算来创建一个新的future值,但是我们没有展示一旦此结果变得可用后如何来使用,以便我们能够用它来做一些有用的事。我们经常对计算结果感兴趣而不仅仅是它的副作用。
+现在我们知道如何开始一个异步计算来创建一个新的future值,但是我们没有展示一旦此结果变得可用后如何来使用,以便我们能够用它来做一些有用的事。
+我们经常对计算结果感兴趣而不仅仅是它的副作用。
-在许多future的实现中,一旦future的client对future的结果感兴趣,它不得不阻塞它自己的计算直到future完成——然后才能使用future的值继续它自己的计算。虽然这在Scala的Future API(在后面会展示)中是允许的,但是从性能的角度来看更好的办法是一种完全非阻塞的方法,即在future中注册一个回调,future完成后这个回调称为异步回调。如果当注册回调时future已经完成,则回调可能是异步执行的,或在相同的线程中循序执行。
+在许多future的实现中,一旦future的client对future的结果感兴趣,它不得不阻塞它自己的计算直到future完成——然后才能使用future的值继续它自己的计算。
+虽然这在Scala的Future API(在后面会展示)中是允许的,但是从性能的角度来看更好的办法是一种完全非阻塞的方法,即在future中注册一个回调。
+future 完成后这个回调称为异步回调。如果当注册回调时 future 已经完成,则回调可能是异步执行的,或在相同的线程中循序执行。
-注册回调最通常的形式是使用OnComplete方法,即创建一个`Try[T] => U`类型的回调函数。如果future成功完成,回调则会应用到Success[T]类型的值中,否则应用到` Failure[T] `类型的值中。
+注册回调最通常的形式是使用 `OnComplete` 方法,即创建一个``Try[T] => U` 类型的回调函数。
+如果future成功完成,回调则会应用到 `Success[T]` 类型的值中,否则应用到 `Failure[T]` 类型的值中。
- `Try[T]` 和`Option[T]`或 `Either[T, S]`相似,因为它是一个可能持有某种类型值的单子。然而,它是特意设计来保持一个值或某个可抛出(throwable)对象。`Option[T]` 既可以是一个值(如:`Some[T]`)也可以是完全无值(如:`None`),如果`Try[T]`获得一个值则它为`Success[T]` ,否则为`Failure[T]`的异常。 `Failure[T]` 获得更多的关于为什么这儿没值的信息,而不仅仅是None。同时也可以把`Try[T]`看作一种特殊版本的`Either[Throwable, T]`,专门用于左值为可抛出类型(Throwable)的情形。
+ `Try[T]` 和`Option[T]`或 `Either[T, S]`相似,因为它是一个可能持有某种类型值的单子。
+ 然而,它是特意设计来保持一个值或某个可抛出(throwable)对象。
+ `Option[T]` 既可以是一个值(如:`Some[T]`)也可以是完全无值(如:`None`),如果 `Try[T]` 获得一个值则它为 `Success[T]`,否则为 `Failure[T]` 的异常。`Failure[T]` 获得更多的关于为什么这儿没值的信息,而不仅仅是 `None`。
+ 同时也可以把 `Try[T]` 看作一种特殊版本的 `Either[Throwable, T]`,专门用于左值为可抛出类型(Throwable)的情形。
-回到我们的社交网络的例子,假设我们想要获取我们最近的帖子并显示在屏幕上,我们通过调用getRecentPosts方法获得一个返回值List[String]——一个近期帖子的列表文本:
+回到我们的社交网络的例子,假设我们想要获取我们最近的帖子并显示在屏幕上,我们通过调用 `getRecentPosts` 方法获得一个返回值 `List[String]` -- 一个近期帖子的列表文本:
- val f: Future[List[String]] = Future {
- session.getRecentPosts
- }
+{% tabs futures-05 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-05 %}
- f onComplete {
- case Success(posts) => for (post <- posts) println(post)
- case Failure(t) => println("An error has occured: " + t.getMessage)
- }
+```scala
+import scala.util.{Success, Failure}
+val f: Future[List[String]] = Future {
+ session.getRecentPosts
+}
-onComplete方法一般在某种意义上它允许客户处理future计算出的成功或失败的结果。对于仅仅处理成功的结果,onSuccess 回调使用如下(该回调以一个偏函数(partial function)为参数):
+f.onComplete {
+ case Success(posts) => for (post <- posts) println(post)
+ case Failure(t) => println("An error has occured: " + t.getMessage)
+}
+```
- val f: Future[List[String]] = Future {
- session.getRecentPosts
- }
+{% endtab %}
+{% tab 'Scala 3' for=futures-05 %}
- f onSuccess {
- case posts => for (post <- posts) println(post)
- }
+```scala
+import scala.util.{Success, Failure}
-对于处理失败结果,onFailure回调使用如下:
+val f: Future[List[String]] = Future {
+ session.getRecentPosts()
+}
- val f: Future[List[String]] = Future {
- session.getRecentPosts
- }
+f.onComplete {
+ case Success(posts) => for post <- posts do println(post)
+ case Failure(t) => println("An error has occurred: " + t.getMessage)
+}
+```
- f onFailure {
- case t => println("An error has occured: " + t.getMessage)
- }
+{% endtab %}
+{% endtabs %}
- f onSuccess {
- case posts => for (post <- posts) println(post)
- }
+`onComplete` 方法一般在某种意义上它允许客户处理future计算出的成功或失败的结果。对于仅仅处理成功的结果,可以使用 `foreach` 回调:
-如果future失败,即future抛出异常,则执行onFailure回调。
+{% tabs futures-06 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-06 %}
-因为偏函数具有 isDefinedAt方法, onFailure方法只有在特定的Throwable类型对象中被定义才会触发。下面例子中的onFailure回调永远不会被触发:
+```scala
+val f: Future[List[String]] = Future {
+ session.getRecentPosts()
+}
- val f = Future {
- 2 / 0
- }
+for {
+ posts <- f
+ post <- posts
+} println(post)
+```
- f onFailure {
- case npe: NullPointerException =>
- println("I'd be amazed if this printed out.")
- }
+{% endtab %}
+{% tab 'Scala 3' for=futures-06 %}
+
+```scala
+val f: Future[List[String]] = Future {
+ session.getRecentPosts()
+}
+
+for
+ posts <- f
+ post <- posts
+do println(post)
+```
+
+{% endtab %}
+{% endtabs %}
+
+`Future` 提供了一个清晰的手段只用来处理失败的结果,这个手段是使用 `failed` 投影,这个投影把 `Failure[Throwable]` 转换成 `Success[Throwable]`。下面的[投影](#投影)章节提供了这样一个例子。
回到前面查找某个关键字第一次出现的例子,我们想要在屏幕上打印出此关键字的位置:
- val firstOccurrence: Future[Int] = Future {
- val source = scala.io.Source.fromFile("myText.txt")
- source.toSeq.indexOfSlice("myKeyword")
- }
+{% tabs futures-oncomplete %}
+{% tab 'Scala 2 and 3' for=futures-oncomplete %}
- firstOccurrence onSuccess {
- case idx => println("The keyword first appears at position: " + idx)
- }
+```scala
+val firstOccurrence: Future[Int] = Future {
+ val source = scala.io.Source.fromFile("myText.txt")
+ source.toSeq.indexOfSlice("myKeyword")
+}
- firstOccurrence onFailure {
- case t => println("Could not process file: " + t.getMessage)
- }
+firstOccurrence.onComplete {
+ case Success(idx) => println("The keyword first appears at position: " + idx)
+ case Failure(t) => println("Could not process file: " + t.getMessage)
+}
+```
- onComplete,、onSuccess 和 onFailure 方法都具有Unit的结果类型,这意味着不能链接使用这些方法的回调。注意这种设计是为了避免暗示而刻意为之的,因为链接回调也许暗示着按照一定的顺序执行注册回调(回调注册在同一个future中是无序的)。
+{% endtab %}
+{% endtabs %}
-也就是说,我们现在应讨论论何时调用callback。因为callback需要future的值是可用的,所有回调只能在future完成之后被调用。然而,不能保证callback在完成future的线程或创建callback的线程中被调用。反而, 回调(callback)会在future对象完成之后的一些线程和一段时间内执行。所以我们说回调(callback)最终会被执行。
+`onComplete` 和 `foreach` 方法都具有 `Unit` 的结果类型,这意味着不能链接使用这些方法的回调。注意这种设计是为了避免暗示而刻意为之的,因为链接回调也许暗示着按照一定的顺序执行注册回调(回调注册在同一个 future 中是无序的)。
-此外,回调(callback)执行的顺序不是预先定义的,甚至在相同的应用程序中callback的执行顺序也不尽相同。事实上,callback也许不是一个接一个连续的调用,但是可能会在同一时间同时执行。这意味着在下面的例子中,变量totalA也许不能在计算上下文中被设置为正确的大写或者小写字母。
+也就是说,我们现在应讨论**何时**正好在调用回调(callback)。因为回调需要 future 的值是可用的,所有回调只能在 future 完成之后被调用。
+然而,不能保证回调在完成 future 的线程或创建回调的线程中被调用。
+反而, 回调会在 future 对象完成之后的一些线程和一段时间内执行。所以我们说回调最终会被执行。
- @volatile var totalA = 0
+此外,回调(callback)执行的顺序不是预先定义的,甚至在相同的应用程序中回调的执行顺序也不尽相同。
+事实上,回调也许不是一个接一个连续的调用,但是可能会在同一时间同时执行。
+这意味着在下面的例子中,变量 `totalA` 也许不能在计算上下文中被设置为正确的大写或者小写字母 `a`。
- val text = Future {
- "na" * 16 + "BATMAN!!!"
- }
+{% tabs volatile %}
+{% tab 'Scala 2 and 3' for=volatile %}
+
+```scala
+@volatile var totalA = 0
- text onSuccess {
- case txt => totalA += txt.count(_ == 'a')
- }
+val text = Future {
+ "na" * 16 + "BATMAN!!!"
+}
- text onSuccess {
- case txt => totalA += txt.count(_ == 'A')
- }
-以上,这两个回调(callbacks)可能是一个接一个地执行的,这样变量totalA得到的预期值为18。然而,它们也可能是并发执行的,于是totalA最终可能是16或2,因为+= 是一个不可分割的操作符(即它是由一个读和一个写的步骤组成,这样就可能使其与其他的读和写任意交错执行)。
+text.foreach { txt =>
+ totalA += txt.count(_ == 'a')
+}
+
+text.foreach { txt =>
+ totalA += txt.count(_ == 'A')
+}
+```
+
+{% endtab %}
+{% endtabs %}
+
+以上,这两个回调(callbacks)可能是一个接一个地执行的,这样变量 `totalA` 得到的预期值为`18`。
+然而,它们也可能是并发执行的,于是 `totalA` 最终可能是`16`或`2`,因为 `+=` 不是一个原子性的操作符(即它是由一个读和一个写的步骤组成,这样就可能使其与其他的读和写任意交错执行)。
考虑到完整性,回调的使用情景列在这儿:
-- 在future中注册onComplete回调的时候要确保最后future执行完成之后调用相应的终止回调。
+1. 在 future 中注册 `onComplete` 回调的时候要确保最后 future 执行完成之后调用相应的终止回调。
-- 注册onSuccess或者onFailure回调时也和注册onComplete一样,不同之处在于future执行成功或失败分别调用onSuccess或onSuccess的对应的闭包。
+2. 注册 `foreach` 回调时也和注册 `onComplete` 一样,不同之处在于 future 成功完成才会调用闭包。
-- 注册一个已经完成的future的回调最后将导致此回调一直处于执行状态(1所隐含的)。
+3. 在一个已经完成的 future 上注册回调将导致此该回调最终被执行(1所隐含的)。
-- 在future中注册多个回调的情况下,这些回调的执行顺序是不确定的。事实上,这些回调也许是同时执行的,然而,特定的ExecutionContext执行可能导致明确的顺序。
+4. 在 future 中注册多个回调的情况下,这些回调的执行顺序是不确定的。事实上,这些回调也许是同时执行的。然而,特定的 `ExecutionContext` 实现可能导致明确的顺序。
-- 在一些回调抛出异常的情况下,其他的回调的执行不受影响。
+5. 在一些回调抛出异常的情况下,其他的回调的执行不受影响。
-- 在一些情况下,回调函数永远不能结束(例如,这些回调处于无限循环中),其他回调可能完全不会执行。在这种情况下,对于那些潜在的阻塞回调要使用阻塞的构造(例子如下)。
+6. 在一些情况下,回调函数永远不能结束(例如,这些回调处于无限循环中),其他回调可能完全不会执行。在这种情况下,对于那些潜在的阻塞回调要使用 `blocking` 的构造(例子如下)。
-- 一旦执行完,回调将从future对象中移除,这样更适合JVM的垃圾回收机制(GC)。
+7. 一旦执行完,回调将从 future 对象中移除,这样更适合垃圾回收机制(GC)。
### 函数组合(Functional Composition)和For解构(For-Comprehensions)
-尽管前文所展示的回调机制已经足够把future的结果和后继计算结合起来的,但是有些时候回调机制并不易于使用,且容易造成冗余的代码。我们可以通过一个例子来说明。假设我们有一个用于进行货币交易服务的API,我们想要在有盈利的时候购进一些美元。让我们先来看看怎样用回调来解决这个问题:
+尽管前文所展示的回调机制已经足够把future的结果和后继计算结合起来的。
+但是有些时候回调机制并不易于使用,且容易造成冗余的代码。
+我们可以通过一个例子来说明。假设我们有一个用于进行货币交易服务的 API,我们只想在有盈利的时候购进一些美元。让我们先来看看怎样用回调来解决这个问题:
- val rateQuote = Future {
- connection.getCurrentValue(USD)
- }
+{% tabs futures-07 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-07 %}
+
+```scala
+val rateQuote = Future {
+ connection.getCurrentValue(USD)
+}
- rateQuote onSuccess { case quote =>
- val purchase = Future {
- if (isProfitable(quote)) connection.buy(amount, quote)
- else throw new Exception("not profitable")
- }
+for (quote <- rateQuote) {
+ val purchase = Future {
+ if (isProfitable(quote)) connection.buy(amount, quote)
+ else throw new Exception("not profitable")
+ }
- purchase onSuccess {
- case _ => println("Purchased " + amount + " USD")
- }
- }
+ for (amount <- purchase)
+ println("Purchased " + amount + " USD")
+}
+```
-首先,我们创建一个名为rateQuote的future对象并获得当前的汇率。在服务器返回了汇率且该future对象成功完成了之后,计算操作才会从onSuccess回调中执行,这时我们就可以开始判断买还是不买了。所以我们创建了另一个名为purchase的future对象,用来在可盈利的情况下做出购买决定,并在稍后发送一个请求。最后,一旦purchase运行结束,我们会在标准输出中打印一条通知消息。
+{% endtab %}
+{% tab 'Scala 3' for=futures-07 %}
-这确实是可行的,但是有两点原因使这种做法并不方便。其一,我们不得不使用onSuccess,且不得不在其中嵌套purchase future对象。试想一下,如果在purchase执行完成之后我们可能会想要卖掉一些其他的货币。这时我们将不得不在onSuccess的回调中重复这个模式,从而可能使代码过度嵌套,过于冗长,并且难以理解。
+```scala
+val rateQuote = Future {
+ connection.getCurrentValue(USD)
+}
-其二,purchase只是定义在局部范围内--它只能被来自onSuccess内部的回调响应。这也就是说,这个应用的其他部分看不到purchase,而且不能为它注册其他的onSuccess回调,比如说卖掉些别的货币。
+for quote <- rateQuote do
+ val purchase = Future {
+ if isProfitable(quote) then connection.buy(amount, quote)
+ else throw Exception("not profitable")
+ }
-为解决上述的两个问题,futures提供了组合器(combinators)来使之具有更多易用的组合形式。映射(map)是最基本的组合器之一。试想给定一个future对象和一个通过映射来获得该future值的函数,映射方法将创建一个新Future对象,一旦原来的Future成功完成了计算操作,新的Future会通过该返回值来完成自己的计算。你能够像理解容器(collections)的map一样来理解future的map。
+ for amount <- purchase do
+ println("Purchased " + amount + " USD")
+```
-让我们用map的方法来重构一下前面的例子:
+{% endtab %}
+{% endtabs %}
- val rateQuote = Future {
- connection.getCurrentValue(USD)
- }
+首先,我们创建一个名为 `rateQuote` 的 future 对象并获得当前的汇率。
+在服务器返回了汇率且该 future 对象成功完成了之后,计算操作才会从 `foreach` 回调中执行,这时我们就可以开始判断买还是不买了。
+所以我们创建了另一个名为 `purchase` 的 future 对象,用来只在可盈利的情况下做出购买决定,然后发送一个请求。
+最后,一旦purchase运行结束,我们会在标准输出中打印一条通知消息。
- val purchase = rateQuote map { quote =>
- if (isProfitable(quote)) connection.buy(amount, quote)
- else throw new Exception("not profitable")
- }
+这确实是可行的,但是有两点原因使这种做法并不方便。其一,我们不得不使用 `foreach`,在其中嵌套第二个 `purchase` future 对象。试想一下,如果在 `purchase` 执行完成之后我们可能会想要卖掉一些其他的货币。这时我们将不得不在 `foreach` 回调中重复这个模式,从而可能使代码过度嵌套,过于冗长,并且难以理解。
- purchase onSuccess {
- case _ => println("Purchased " + amount + " USD")
- }
+其二,`purchase` future 不在余下的代码范围内 -- 它只能被来自 `foreach` 内部的回调响应。这也就是说,这个应用的其他部分看不到 `purchase`,而且不能为它注册其他的 `foreach` 回调,比如说卖掉些别的货币。
-通过对rateQuote的映射我们减少了一次onSuccess的回调,更重要的是避免了嵌套。这时如果我们决定出售一些货币就可以再次使用purchase方法上的映射了。
+为解决上述的两个问题, futures提供了组合器(combinators)来使之具有更多易用的组合形式。映射(map)是最基本的组合器之一。试想给定一个 future 对象和一个通过映射来获得该 future 值的函数,映射方法将创建一个新 Future 对象,一旦原来的 Future 成功完成了计算操作,新的 Future 会通过该返回值来完成自己的计算。你能够像理解容器(collections)的map一样来理解 future 的map。
-可是如果isProfitable方法返回了false将会发生些什么?会引发异常?这种情况下,purchase的确会因为异常而失败。不仅仅如此,想象一下,链接的中断和getCurrentValue方法抛出异常会使rateQuote的操作失败。在这些情况下映射将不会返回任何值,而purchase也会会自动的以和rateQuote相同的异常而执行失败。
+让我们用 `map` 组合器来重构一下前面的例子:
-总之,如果原Future的计算成功完成了,那么返回的Future将会使用原Future的映射值来完成计算。如果映射函数抛出了异常则Future也会带着该异常完成计算。如果原Future由于异常而计算失败,那么返回的Future也会包含相同的异常。这种异常的传导方式也同样适用于其他的组合器(combinators)。
+{% tabs futures-08 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-08 %}
-使之能够在For-comprehensions原则下使用,是设计Future的目的之一。也正是因为这个原因,Future还拥有flatMap,filter和foreach等组合器。其中flatMap方法可以构造一个函数,它可以把值映射到一个姑且称为g的新future,然后返回一个随g的完成而完成的Future对象。
+```scala
+val rateQuote = Future {
+ connection.getCurrentValue(USD)
+}
-让我们假设我们想把一些美元兑换成瑞士法郎。我们必须为这两种货币报价,然后再在这两个报价的基础上确定交易。下面是一个在for-comprehensions中使用flatMap和withFilter的例子:
+val purchase = rateQuote map { quote =>
+ if (isProfitable(quote)) connection.buy(amount, quote)
+ else throw new Exception("not profitable")
+}
- val usdQuote = Future { connection.getCurrentValue(USD) }
- val chfQuote = Future { connection.getCurrentValue(CHF) }
+purchase.foreach { amount =>
+ println("Purchased " + amount + " USD")
+}
+```
- val purchase = for {
- usd <- usdQuote
- chf <- chfQuote
- if isProfitable(usd, chf)
- } yield connection.buy(amount, chf)
+{% endtab %}
+{% tab 'Scala 3' for=futures-08 %}
- purchase onSuccess {
- case _ => println("Purchased " + amount + " CHF")
- }
+```scala
+val rateQuote = Future {
+ connection.getCurrentValue(USD)
+}
-purchase只有当usdQuote和chfQuote都完成计算以后才能完成-- 它以其他两个Future的计算值为前提所以它自己的计算不能更早的开始。
+val purchase = rateQuote.map { quote =>
+ if isProfitable(quote) then connection.buy(amount, quote)
+ else throw Exception("not profitable")
+}
-上面的for-comprhension将被转换为:
+purchase.foreach { amount =>
+ println("Purchased " + amount + " USD")
+}
+```
- val purchase = usdQuote flatMap {
- usd =>
- chfQuote
- .withFilter(chf => isProfitable(usd, chf))
- .map(chf => connection.buy(amount, chf))
- }
+{% endtab %}
+{% endtabs %}
+
+通过在 `rateQuote` 上使用 `map`,我们减少了一次 `foreach` 的回调,更重要的是避免了嵌套。这时如果我们决定出售一些货币就可以再次在 `purchase` 上使用 `map` 了。
+
+可是如果 `isProfitable` 方法返回了 `false` 将会发生些什么?会引发异常?
+这种情况下,`purchase` 的确会因为异常而失败。不仅仅如此,想象一下,链接的中断和 `getCurrentValue` 方法抛出异常会使 `rateQuote` 的操作失败。
+在这些情况下映射将不会返回任何值,而 `purchase` 也会自动的以和 `rateQuote` 相同的异常而失败。
+
+总之,如果原 future 成功完成了,那么返回的 future 将会使用从原 future来的映射值完成。如果映射函数抛出了异常则返回的 future 也会带着一样的异常。如果原 future 由于异常而计算失败,那么返回的 future 也会包含相同的异常。这种异常的传导语义也存在于其余的组合器(combinators)。
+
+使之能够在for-comprehensions 中使用,是设计 future 的目的之一。
+因为这个原因,future 还拥有 `flatMap` 和 `withFilter` 组合器。`flatMap` 方法获取一个函数,该函数把值映射到一个新 future `g`,然后返回一个随 `g` 的完成而完成的 future。
+
+让我们假设我们想把一些美元兑换成瑞士法郎(CHF)。我们必须为这两种货币报价,然后再在这两个报价的基础上确定交易。
+下面是一个在 for-comprehensions 中使用 `flatMap` 和 `withFilter` 的例子:
+
+{% tabs futures-09 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-09 %}
+
+```scala
+val usdQuote = Future { connection.getCurrentValue(USD) }
+val chfQuote = Future { connection.getCurrentValue(CHF) }
+
+val purchase = for {
+ usd <- usdQuote
+ chf <- chfQuote
+ if isProfitable(usd, chf)
+} yield connection.buy(amount, chf)
-这的确是比for-comprehension稍微难以把握一些,但是我们这样分析有助于您更容易的理解flatMap的操作。FlatMap操作会把自身的值映射到其他future对象上,并随着该对象计算完成的返回值一起完成计算。在我们的例子里,flatMap用usdQuote的值把chfQuote的值映射到第三个futrue对象里,该对象用于发送一定量瑞士法郎的购入请求。只有当通过映射返回的第三个future对象完成了计算,purchase才能完成计算。
+purchase foreach { amount =>
+ println("Purchased " + amount + " CHF")
+}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=futures-09 %}
+
+```scala
+val usdQuote = Future { connection.getCurrentValue(USD) }
+val chfQuote = Future { connection.getCurrentValue(CHF) }
+
+val purchase = for
+ usd <- usdQuote
+ chf <- chfQuote
+ if isProfitable(usd, chf)
+yield connection.buy(amount, chf)
+
+purchase.foreach { amount =>
+ println("Purchased " + amount + " CHF")
+}
+```
+
+{% endtab %}
+{% endtabs %}
+
+`purchase` 只有当 `usdQuote` 和 `chfQuote` 都完成计算以后才能完成-- 它以其他两个 future 的计算值为前提所以它自己的计算不能更早的开始。
+
+上面的 for-comprhension 将被转换为:
+
+{% tabs for-translation %}
+{% tab 'Scala 2 and 3' for=for-translation %}
+
+```scala
+val purchase = usdQuote flatMap {
+ usd =>
+ chfQuote
+ .withFilter(chf => isProfitable(usd, chf))
+ .map(chf => connection.buy(amount, chf))
+}
+```
+
+{% endtab %}
+{% endtabs %}
+
+这的确是比for-comprehension稍微难以把握一些,但是我们这样分析有助于您更容易的理解 `flatMap` 的操作。`flatMap` 操作会把自身的值映射到其他future对象上,并随着该对象计算完成的返回值一起完成计算。
+在我们的例子里,`flatMap` 用 `usdQuote` 的值把 `chfQuote` 的值映射到第三个 futrue 对象里,该对象用于发送一定量瑞士法郎的购入请求。
+只有当通过 `map` 返回的第三个 future 对象完成,结果 future `purchase` 才能完成。
这可能有些难以置信,但幸运的是faltMap操作在for-comprhensions模式以外很少使用,因为for-comprehensions本身更容易理解和使用。
-再说说filter,它可以用于创建一个新的future对象,该对象只有在满足某些特定条件的前提下才会得到原始future的计算值,否则就会抛出一个NoSuchElementException的异常而失败。调用了filter的future,其效果与直接调用withFilter完全一样。
+`filter` 组合器可以用于创建一个新的 future 对象,该对象只有在满足某些特定条件的前提下才会得到原始future的值。否则新 future 就会有 `NoSuchElementException` 的失败。调用了 `filter` 的future,其效果与直接调用 `withFilter` 完全一样。
-作为组合器的collect同filter之间的关系有些类似容器(collections)API里的那些方法之间的关系。
+作为组合器的 `collect` 同 `filter` 之间的关系有些类似容器(collections)API里的那些方法之间的关系。
-值得注意的是,调用foreach组合器并不会在计算值可用的时候阻塞当前的进程去获取计算值。恰恰相反,只有当future对象成功计算完成了,foreach所迭代的函数才能够被异步的执行。这意味着foreach与onSuccess回调意义完全相同。
+由于 `Future` trait(译注: trait有点类似java中的接口(interface)的概念)从概念上看可以包含两种类型的返回值(计算结果和异常),所以组合器会有一个处理异常的需求。
-由于Future trait(译注: trait有点类似java中的接口(interface)的概念)从概念上看包含两种类型的返回值(计算结果和异常),所以组合器会有一个处理异常的需求。
+比方说我们准备在 `rateQuote` 的基础上决定购入一定量的货币,那么 `connection.buy` 方法需要获取购入的 `amount` 和期望的 `quote`。它返回完成购买的数量。假如 `quote` 偏偏在这个节骨眼儿改变了,那buy方法将会抛出一个 `QuoteChangedExecption`,并且不会做任何交易。如果我们想让我们的 future 对象返回`0`而不是抛出那个异常,那我们需要使用 `recover` 组合器:
-比方说我们准备在rateQuote的基础上决定购入一定量的货币,那么`connection.buy`方法需要知道购入的数量和期望的报价值,最终完成购买的数量将会被返回。假如报价值偏偏在这个节骨眼儿改变了,那buy方法将会抛出一个`QuoteChangedExecption`,并且不会做任何交易。如果我们想让我们的Future对象返回0而不是抛出那个该死的异常,那我们需要使用recover组合器:
- val purchase: Future[Int] = rateQuote map {
- quote => connection.buy(amount, quote)
- } recover {
- case QuoteChangedException() => 0
- }
+{% tabs recover %}
+{% tab 'Scala 2 and 3' for=recover %}
-这里用到的recover能够创建一个新future对象,该对象当计算完成时持有和原future对象一样的值。如果执行不成功则偏函数的参数会被传递给使原Future失败的那个Throwable异常。如果它把Throwable映射到了某个值,那么新的Future就会成功完成并返回该值。如果偏函数没有定义在Throwable中,那么最终产生结果的future也会失败并返回同样的Throwable。
+```scala
+val purchase: Future[Int] = rateQuote.map {
+ quote => connection.buy(amount, quote)
+} recover {
+ case QuoteChangedException() => 0
+}
+```
+{% endtab %}
+{% endtabs %}
-组合器recoverWith能够创建一个新future对象,当原future对象成功完成计算时,新future对象包含有和原future对象相同的计算结果。若原future失败或异常,偏函数将会返回造成原future失败的相同的Throwable异常。如果此时Throwable又被映射给了别的future,那么新Future就会完成并返回这个future的结果。recoverWith同recover的关系跟flatMap和map之间的关系很像。
+这里用到的 `recover` 能够创建一个新 future 对象,该对象当成功完成时持有和原 future 对象一样的值。如果执行不成功则偏函数的参数会被传递给使原 future 失败的那个 `Throwable` 异常。如果它把 `Throwable` 映射到了某个值,那么新的 future 就会成功完成并返回该值。
+如果偏函数没有定义在 `Throwable` 中,那么最终产生结果的 future 也会失败并返回同样的 `Throwable`。
-fallbackTo组合器生成的future对象可以在该原future成功完成计算时返回结果,如果原future失败或异常返回future参数对象的成功值。在原future和参数future都失败的情况下,新future对象会完成并返回原future对象抛出的异常。正如下面的例子中,本想打印美元的汇率,但是在获取美元汇率失败的情况下会打印出瑞士法郎的汇率:
+组合器 `recoverWith` 能够创建一个新 future 对象,当原 future 对象成功完成计算时,新 future 包含有和原 future 相同的结果。若原 future 失败或异常,偏函数将会返回造成原 future 失败的相同的 `Throwable` 异常。如果此时 `Throwable` 又被映射给了别的 future ,那么新 future 就会完成并返回这个 future 的结果。
+`recoverWith` 同 `recover` 的关系跟 `flatMap` 和 `map` 之间的关系很像。
- val usdQuote = Future {
- connection.getCurrentValue(USD)
- } map {
- usd => "Value: " + usd + "$"
- }
- val chfQuote = Future {
- connection.getCurrentValue(CHF)
- } map {
- chf => "Value: " + chf + "CHF"
- }
+`fallbackTo` 组合器生成的 future 对象可以在该原 future 成功完成计算时返回结果,如果原 future 失败或异常返回 future 参数对象的成功值。在原 future 和参数 future 都失败的情况下,新 future 对象会完成并返回原 future 对象抛出的异常。正如下面的例子中,本想打印美元的汇率,但是在获取美元汇率失败的情况下会打印出瑞士法郎的汇率:
- al anyQuote = usdQuote fallbackTo chfQuote
+{% tabs fallback-to %}
+{% tab 'Scala 2 and 3' for=fallback-to %}
- anyQuote onSuccess { println(_) }
+```scala
+val usdQuote = Future {
+ connection.getCurrentValue(USD)
+}.map {
+ usd => "Value: " + usd + "$"
+}
+val chfQuote = Future {
+ connection.getCurrentValue(CHF)
+} map {
+ chf => "Value: " + chf + "CHF"
+}
-组合器andThen的用法是出于纯粹的side-effecting目的。经andThen返回的新Future无论原Future成功或失败都会返回与原Future一模一样的结果。一旦原Future完成并返回结果,andThen后跟的代码块就会被调用,且新Future将返回与原Future一样的结果,这确保了多个andThen调用的顺序执行。正如下例所示,这段代码可以从社交网站上把近期发出的帖子收集到一个可变集合里,然后把它们都打印在屏幕上:
+val anyQuote = usdQuote.fallbackTo(chfQuote)
- val allposts = mutable.Set[String]()
+anyQuote.foreach { println(_) }
+```
- Future {
- session.getRecentPosts
- } andThen {
- case Success(posts) => allposts ++= posts
- } andThen {
- case _ =>
- clearAll()
- for (post <- allposts) render(post)
- }
+{% endtab %}
+{% endtabs %}
-综上所述,Future的组合器功能是纯函数式的,每种组合器都会返回一个与原Future相关的新Future对象。
+组合器 `andThen` 的用法是出于纯粹的side-effecting目的。经 `andThen` 返回的新 future 无论原 future 成功或失败都会返回与原 future 一模一样的结果。
+一旦原 future 完成并返回结果,`andThen` 后跟的代码块就会被调用,且新 future 将返回与原 future 一样的结果,这确保了多个 `andThen` 调用的顺序执行。正如下例所示,这段代码可以从社交网站上把近期发出的帖子收集到一个可变集合里,然后把它们都打印在屏幕上:
-### 投影(Projections)
+{% tabs futures-10 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-10 %}
-为了确保for解构(for-comprehensions)能够返回异常,futures也提供了投影(projections)。如果原future对象失败了,失败的投影(projection)会返回一个带有Throwable类型返回值的future对象。如果原Future成功了,失败的投影(projection)会抛出一个NoSuchElementException异常。下面就是一个在屏幕上打印出异常的例子:
+```scala
+val allposts = mutable.Set[String]()
- val f = Future {
- 2 / 0
- }
- for (exc <- f.failed) println(exc)
+Future {
+ session.getRecentPosts
+} andThen {
+ case Success(posts) => allposts ++= posts
+} andThen {
+ case _ =>
+ clearAll()
+ for (post <- allposts) render(post)
+}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=futures-10 %}
+
+```scala
+val allPosts = mutable.Set[String]()
+
+Future {
+ session.getRecentPosts()
+}.andThen {
+ case Success(posts) => allPosts ++= posts
+}.andThen {
+ case _ =>
+ clearAll()
+ for post <- allPosts do render(post)
+}
+```
+{% endtab %}
+{% endtabs %}
+
+综上所述,在 future 上的组合器功能是纯函数式的。
+每种组合器都会返回一个与原future相关的新 future 对象。
+### 投影
+
+为了确保for解构(for-comprehensions)能够返回异常, future s也提供了投影(projections)。如果原 future 对象失败了,`failed` 的投影会返回一个带有 `Throwable` 类型返回值的 future 对象。如果原 future 成功了,`failed` 的投影失败并有一个 `NoSuchElementException`。下面就是一个在屏幕上打印出异常的例子:
+
+{% tabs futures-11 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-11 %}
+
+```scala
+val f = Future {
+ 2 / 0
+}
+for (exc <- f.failed) println(exc)
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=futures-11 %}
+
+```scala
+val f = Future {
+ 2 / 0
+}
+for exc <- f.failed do println(exc)
+```
+
+{% endtab %}
+{% endtabs %}
+
+本例中的 for-comprehension 翻译成:
+
+{% tabs for-comp-tran %}
+{% tab 'Scala 2 and 3' for=for-comp-tran %}
+
+```scala
+f.failed.foreach(exc => println(exc))
+```
+
+{% endtab %}
+{% endtabs %}
+
+因为 `f` 在这没有成功,该闭包被注册到新成功的 `Future[Throwable]` 上的 `foreach`。
下面的例子不会在屏幕上打印出任何东西:
- val f = Future {
+{% tabs futures-12 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-12 %}
+
+```scala
+val f = Future {
+ 4 / 2
+}
+for (exc <- f.failed) println(exc)
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=futures-12 %}
+
+```scala
+val g = Future {
+ 4 / 2
+}
+for exc <- g.failed do println(exc)
+```
+
+{% endtab %}
+{% endtabs %}
+
+
+
+
+
+
### Future的扩展
-用更多的实用方法来对Futures API进行扩展支持已经被提上了日程,这将为很多外部框架提供更多专业工具。
+用更多的实用方法来对 Futures API 进行扩展支持已经被提上了日程,这将为很多外部框架提供更多专业工具。
-## Blocking
+## 阻塞(Blocking)
-正如前面所说的,在future的blocking非常有效地缓解性能和预防死锁。虽然在futures中使用这些功能方面的首选方式是Callbacks和combinators,但在某些处理中也会需要用到blocking,并且它也是被Futures and Promises API所支持的。
+Future 通常是异步的,不会阻塞底层执行线程。
+但是,在某些情况下,有必要阻塞。
+我们区分了两种形式的阻塞执行线程:
+调用任意代码,从 future 来内部阻塞线程,
+以及从另一个 future 外部阻塞,等待该 future 完成。
-在之前的并发交易(concurrency trading)例子中,在应用的最后有一处用到block来确定是否所有的futures已经完成。这有个如何使用block来处理一个future结果的例子:
+### Future 内的阻塞
- import scala.concurrent._
- import scala.concurrent.duration._
+正如在全局 `ExecutionContext` 中看到的那样,可以使用 `blocking` 结构通知某个阻塞调用的 `ExecutionContext`。
+但是,实现完全由 `ExecutionContext`。一些 `ExecutionContext`,如 `ExecutionContext.global`,用 `MangedBlocker` 的办法实现 `blocking`,而另外一样通过执行上下文,如固定线程池来实现 `blocking`:
+通过“ManagedBlocker”实现“阻塞”,一些执行上下文,如固定线程池:
- def main(args: Array[String]): Unit = {
- val rateQuote = Future {
- connection.getCurrentValue(USD)
- }
+{% tabs fixed-thread-pool %}
+{% tab 'Scala 2 and 3' for=fixed-thread-pool %}
- val purchase = rateQuote map { quote =>
- if (isProfitable(quote)) connection.buy(amount, quote)
- else throw new Exception("not profitable")
- }
+```scala
+ExecutionContext.fromExecutor(Executors.newFixedThreadPool(x))
+```
- Await.result(purchase, 0 nanos)
- }
+{% endtab %}
+{% endtabs %}
-在这种情况下这个future是不成功的,这个调用者转发出了该future对象不成功的异常。它包含了失败的投影(projection)-- 阻塞(blocking)该结果将会造成一个NoSuchElementException异常在原future对象被成功计算的情况下被抛出。
+将不作任何事,就像下面演示的那样:
-相反的,调用`Await.ready`来等待这个future直到它已完成,但获不到它的结果。同样的方式,调用那个方法时如果这个future是失败的,它将不会抛出异常。
+{% tabs futures-13 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-13 %}
-The Future trait实现了Awaitable trait还有其`ready()`和`result()`方法。这些方法不能被客户端直接调用,它们只能通过执行环境上下文来进行调用。
+```scala
+implicit val ec =
+ ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4))
-为了允许程序调用可能是阻塞式的第三方代码,而又不必实现Awaitable特质,原函数可以用如下的方式来调用:
+Future {
+ blocking { blockingStuff() }
+}
+```
- blocking {
- potentiallyBlockingCall()
- }
+{% endtab %}
+{% tab 'Scala 3' for=futures-13 %}
-这段blocking代码也可以抛出一个异常。在这种情况下,这个异常会转发给调用者。
+```scala
+given ExecutionContext =
+ ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4))
-## 异常(Exceptions)
+Future {
+ blocking { blockingStuff() }
+}
+```
-当异步计算抛出未处理的异常时,与那些计算相关的futures就失败了。失败的futures存储了一个Throwable的实例,而不是返回值。Futures提供onFailure回调方法,它用一个PartialFunction去表示一个Throwable。下列特殊异常的处理方式不同:
+{% endtab %}
+{% endtabs %}
-`scala.runtime.NonLocalReturnControl[_]` --此异常保存了一个与返回相关联的值。通常情况下,在方法体中的返回结构被调用去抛出这个异常。相关联的值将会存储到future或一个promise中,而不是一直保存在这个异常中。
+和以下代码有一样的副作用:
-ExecutionException-当因为一个未处理的中断异常、错误或者`scala.util.control.ControlThrowable`导致计算失败时会被存储起来。这种情况下,ExecutionException会为此具有未处理的异常。这些异常会在执行失败的异步计算线程中重新抛出。这样做的目的,是为了防止正常情况下没有被客户端代码处理过的那些关键的、与控制流相关的异常继续传播下去,同时告知客户端其中的future对象是计算失败的。
+{% tabs alternative %}
+{% tab 'Scala 2 and 3' for=alternative %}
-更精确的语义描述请参见 [NonFatal]。
+```scala
+Future { blockingStuff() }
+```
-## Promises
+{% endtab %}
+{% endtabs %}
-到目前为止,我们仅考虑了通过异步计算的方式创建future对象来使用future的方法。尽管如此,futures也可以使用promises来创建。
+阻塞代码也可能抛出异常。这种情况下,异常被转发给调用者。
-如果说futures是为了一个还没有存在的结果,而当成一种只读占位符的对象类型去创建,那么promise就被认为是一个可写的,可以实现一个future的单一赋值容器。这就是说,promise通过这种success方法可以成功去实现一个带有值的future。相反的,因为一个失败的promise通过failure方法就会实现一个带有异常的future。
+### Future 外部的阻塞
-一个promise p通过p.future方式返回future。 这个futrue对象被指定到promise p。根据这种实现方式,可能就会出现p.future与p相同的情况。
+正如前面所说的,为了性能和防止死锁,强烈建议不要在future上用阻塞。
+虽然在futures中使用这些功能方面的首选方式是回调和组合器,但在某些处理中也会需要用到阻塞,并且 Futures API 和 Promises API也支持它。
-考虑下面的生产者 - 消费者的例子,其中一个计算产生一个值,并把它转移到另一个使用该值的计算。这个传递中的值通过一个promise来完成。
+在之前的并发交易(concurrency trading)例子中,在应用的最后有一处用到阻塞来确定是否所有的 futures已经完成。这有个如何使用阻塞来处理一个 future 结果的例子:
- import scala.concurrent.{ Future, Promise }
- import scala.concurrent.ExecutionContext.Implicits.global
+{% tabs futures-14 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-14 %}
- val p = Promise[T]()
- val f = p.future
+```scala
+import scala.concurrent._
+import scala.concurrent.duration._
- val producer = Future {
- val r = produceSomething()
- p success r
- continueDoingSomethingUnrelated()
+object awaitPurchase {
+ def main(args: Array[String]): Unit = {
+ val rateQuote = Future {
+ connection.getCurrentValue(USD)
}
- val consumer = Future {
- startDoingSomething()
- f onSuccess {
- case r => doSomethingWithResult()
- }
+ val purchase = rateQuote map { quote =>
+ if (isProfitable(quote)) connection.buy(amount, quote)
+ else throw new Exception("not profitable")
}
-在这里,我们创建了一个promise并利用它的future方法获得由它实现的Future。然后,我们开始了两种异步计算。第一种做了某些计算,结果值存放在r中,通过执行promise p,这个值被用来完成future对象f。第二种做了某些计算,然后读取实现了future f的计算结果值r。需要注意的是,在生产者完成执行`continueDoingSomethingUnrelated()` 方法这个任务之前,消费者可以获得这个结果值。
+ Await.result(purchase, 0.nanos)
+ }
+}
+```
-正如前面提到的,promises具有单赋值语义。因此,它们仅能被实现一次。在一个已经计算完成的promise或者failed的promise上调用success方法将会抛出一个IllegalStateException异常。
+{% endtab %}
+{% tab 'Scala 3' for=futures-14 %}
-下面的这个例子显示了如何fail a promise。
+```scala
+import scala.concurrent.*
+import scala.concurrent.duration.*
- val p = promise[T]
- val f = p.future
+@main def awaitPurchase =
+ val rateQuote = Future {
+ connection.getCurrentValue(USD)
+ }
- val producer = Future {
- val r = someComputation
- if (isInvalid(r))
- p failure (new IllegalStateException)
- else {
- val q = doSomeMoreComputation(r)
- p success q
- }
- }
+ val purchase = rateQuote.map { quote =>
+ if isProfitable(quote) then connection.buy(amount, quote)
+ else throw Exception("not profitable")
+ }
-如上,生产者计算出一个中间结果值r,并判断它的有效性。如果它不是有效的,它会通过返回一个异常实现promise p的方式fails the promise,关联的future f是failed。否则,生产者会继续它的计算,最终使用一个有效的结果值实现future f,同时实现 promise p。
+ Await.result(purchase, 0.nanos)
+```
-Promises也能通过一个complete方法来实现,这个方法采用了一个`potential value Try[T]`,这个值要么是一个类型为`Failure[Throwable]`的失败的结果值,要么是一个类型为`Success[T]`的成功的结果值。
+{% endtab %}
+{% endtabs %}
-类似success方法,在一个已经完成(completed)的promise对象上调用failure方法和complete方法同样会抛出一个IllegalStateException异常。
+在这种情况下这个 future 是不成功的,这个调用者转发出了该 future 对象不成功的异常。它包含了 `failed` 的投影(projection)-- 阻塞(blocking)该结果将会造成一个 `NoSuchElementException` 异常在原 future 对象被成功计算的情况下被抛出。
-应用前面所述的promises和futures方法的一个优点是,这些方法是单一操作的并且是没有副作用(side-effects)的,因此程序是具有确定性的(deterministic)。确定性意味着,如果该程序没有抛出异常(future的计算值被获得),无论并行的程序如何调度,那么程序的结果将会永远是一样的。
+相反的,调用`Await.ready`来等待这个future直到它已完成,但获不到它的结果。同样的方式,调用那个方法时如果这个future是失败的,它将不会抛出异常。
-在一些情况下,客户端也许希望能够只在promise没有完成的情况下完成该promise的计算(例如,如果有多个HTTP请求被多个不同的futures对象来执行,并且客户端只关心第一个HTTP应答(response),该应答对应于第一个完成该promise的future)。因为这个原因,future提供了tryComplete,trySuccess和tryFailure方法。客户端需要意识到调用这些的结果是不确定的,调用的结果将以来从程序执行的调度。
+The `Future` trait实现了 `Awaitable` trait 还有其 `ready()` 和 `result()` 方法。这些方法不能被客户端直接调用,它们只能通过执行环境上下文来进行调用。
-completeWith方法将用另外一个future完成promise计算。当该future结束的时候,该promise对象得到那个future对象同样的值,如下的程序将打印1:
+## 异常(Exceptions)
- val f = Future { 1 }
- val p = promise[Int]
+当异步计算抛出未处理的异常时,与那些计算相关的 futures 就失败了。失败的 futures 存储了一个 `Throwable` 的实例,而不是返回值。`Futures` 提供 `failed` 投影方法,它允许这个 `Throwable` 被当作另一个 `Future` 的成功值来处理。下列特殊异常的处理方式不同:
+1. `scala.runtime.NonLocalReturnControl[_]` -- 此异常保存了一个与返回相关联的值。通常情况下,方法体中的 `return` 结构被翻译成带有异常的 `throw` 。相关联的值将会存储到future或一个promise中,而不是一直保存在这个异常中。
- p completeWith f
+2. `ExecutionException` -- 当因为一个未处理的 `InterruptedException`, `Error` 或者 `scala.util.control.ControlThrowable` 导致计算失败时会被存储起来。这种情况下, `ExecutionException` 会为此具有未处理的异常。这些异常会在执行失败的异步计算线程中重新抛出。这样做的目的,是为了防止正常情况下没有被客户端代码处理过的那些关键的、与控制流相关的异常继续传播下去,同时告知客户端其中的 future 对象是计算失败的。
- p.future onSuccess {
- case x => println(x)
- }
+致命异常(由 `NonFatal` 确定)在执行失败的异步计算的线程中重新引发。这会通知管理问题执行线程的代码,并允许它在必要时快速失败。更精确的语义描述请参见[`NonFatal`](https://www.scala-lang.org/api/current/scala/util/control/NonFatal$.html)。
+
+## Promise
+
+到目前为止,我们仅考虑了通过异步计算的方式创建 `Future` 对象来使用 `Future` 的方法。尽管如此,futures也可以使用 *promises* 来创建。
+
+如果说futures是为了一个还没有存在的结果,而当成一种只读占位符的对象类型去创建,那么promise就被认为是一个可写的,可以实现一个future的单一赋值容器。这就是说,promise通过这种 `success` 方法可以成功去实现一个带有值(通过 “实现” 该 promise)的future。相反的,用 `failure` 方法。
+一个 promise 也能用来实现带有异常的 future,通过这个失败的promise。
+
+一个promise `p` 通过 `p.future` 方式返回future。 这个futrue对象被指定到promise `p`。根据这种实现方式,可能就会出现 `p.future eq p` 的情况。
+
+考虑下面的生产者-消费者的例子,其中一个计算产生一个值,并把它转移到另一个使用该值的计算。这个传递中的值通过一个 promise 来完成。
+
+{% tabs promises %}
+{% tab 'Scala 2 and 3' for=promises %}
+
+```scala
+import scala.concurrent.{ Future, Promise }
+import scala.concurrent.ExecutionContext.Implicits.global
+
+val p = Promise[T]()
+val f = p.future
+
+val producer = Future {
+ val r = produceSomething()
+ p.success(r)
+ continueDoingSomethingUnrelated()
+}
+
+val consumer = Future {
+ startDoingSomething()
+ f.foreach { r =>
+ doSomethingWithResult()
+ }
+}
+```
+
+{% endtab %}
+{% endtabs %}
+
+在这里,我们创建了一个promise并利用它的 `future` 方法获得由它实现的 `Future`。然后,我们开始了两种异步计算。第一种做了某些计算,结果值存放在r中,通过执行promise `p`,这个值被用来完成future对象 `f`。第二种做了某些计算,然后读取实现了 future `f` 的计算结果值 `r`。需要注意的是,在 `producer` 完成执行 `continueDoingSomethingUnrelated()` 方法这个任务之前,消费者可以获得这个结果值。
+
+正如前面提到的,promises 具有单赋值语义。因此,它们仅能被完成一次。在一个已经计算完成的 promise 或者 failed 的promise上调用 `success` 方法将会抛出一个 `IllegalStateException` 异常。
+
+下面的这个例子显示了如何使 promise 失败。
+
+{% tabs futures-15 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-15 %}
+
+```scala
+val p = Promise[T]()
+val f = p.future
+
+val producer = Future {
+ val r = someComputation
+ if (isInvalid(r))
+ p.failure(new IllegalStateException)
+ else {
+ val q = doSomeMoreComputation(r)
+ p.success(q)
+ }
+}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=futures-15 %}
+
+```scala
+val p = Promise[T]()
+val f = p.future
+
+val producer = Future {
+ val r = someComputation
+ if isInvalid(r) then
+ p.failure(new IllegalStateException)
+ else
+ val q = doSomeMoreComputation(r)
+ p.success(q)
+}
+```
+
+{% endtab %}
+{% endtabs %}
+
+如上, `producer` 计算出一个中间结果值 `r`,并判断它的有效性。如果它不是有效的,它会通过返回一个异常实现promise `p` 的方式fails the promise,关联的future `f` 是失败的。否则, `producer` 会继续它的计算,最终使用一个有效的结果值实现future f,同时实现 promise p。
+
+Promises也能通过一个complete方法来实现,这个方法采用了一个 `potential value Try[T]`,这个值要么是一个类型为 `Failure[Throwable]` 的失败的结果值,要么是一个类型为 `Success[T]` 的成功的结果值。
+
+类似 `success` 方法,在一个已经完成(completed)的promise对象上调用 `failure` 方法和 `complete` 方法同样会抛出一个 `IllegalStateException` 异常。
+
+应用前面所述的 promises 和 futures 操作的一个优点是,这些方法是单一操作的并且是没有副作用(side-effects)的,因此程序是具有确定性的(deterministic)。确定性意味着,如果该程序没有抛出异常(future 的计算值被获得),无论并行的程序如何调度,那么程序的结果将会永远是一样的。
+
+在一些情况下,客户端也许希望能够只在 promise 没有完成的情况下完成该 promise 的计算(例如,如果有多个HTTP请求被多个不同的futures对象来执行,并且客户端只关心第一个HTTP应答(response),该应答对应于第一个完成该 promise 的future)。因为这些原因,future提供了 `tryComplete`,`trySuccess` 和 `tryFailure` 方法。客户端需要意识到调用这些的结果是不确定的,调用的结果将以来从程序执行的调度。
+
+`completeWith` 方法将用另外一个 future 完成 promise 计算。当该 future 结束的时候,该 promise 对象得到那个 future 对象同样的值,如下的程序将打印 `1`:
-当让一个promise以异常失败的时候,三总子类型的Throwable异常被分别的处理。如果中断该promise的可抛出(Throwable)一场是`scala.runtime.NonLocalReturnControl`,那么该promise将以对应的值结束;如果是一个Error的实例,`InterruptedException`或者`scala.util.control.ControlThrowable`,那么该可抛出(Throwable)异常将会封装一个ExecutionException异常,该ExectionException将会让该promise以失败结束。
+{% tabs promises-2 %}
+{% tab 'Scala 2 and 3' for=promises-2 %}
-通过使用promises,futures的onComplete方法和future的构造方法,你能够实现前文描述的任何函数式组合组合器(compition combinators)。让我们来假设一下你想实现一个新的组合起,该组合器首先使用两个future对象f和,产生第三个future,该future能够用f或者g来完成,但是只在它能够成功完成的情况下。
+```scala
+val f = Future { 1 }
+val p = promise[Int]()
+
+p.completeWith(f)
+
+p.future.foreach { x =>
+ println(x)
+}
+```
+
+{% endtab %}
+{% endtabs %}
+
+当让一个promise以异常失败的时候,三个子类型的 `Throwable` 异常被分别的处理。如果中断该promise的可抛出(Throwable)一场是`scala.runtime.NonLocalReturnControl`,那么该promise将以对应的值结束;如果是一个Error的实例,`InterruptedException` 或者 `scala.util.control.ControlThrowable`,那么该 `Throwable` 异常将会封装一个 `ExecutionException` 异常,该 `ExectionException` 将会让该 promise 以失败结束。
+
+通过使用 promises,futures 的 `onComplete` 方法和 `future` 的构造方法,你能够实现前文描述的任何函数式组合组合器(compition combinators)。
+让我们来假设一下你想实现一个新的组合器 `first`,该组合器使用两个future `f` 和 `g`,然后生产出第三个 future,该future能够用 `f` 或者 `g` 来完成(看哪一个先到),但前提是它能够成功。
这里有个关于如何去做的实例:
- def first[T](f: Future[T], g: Future[T]): Future[T] = {
- val p = promise[T]
+{% tabs futures-16 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-16 %}
- f onSuccess {
- case x => p.trySuccess(x)
- }
+```scala
+def first[T](f: Future[T], g: Future[T]): Future[T] = {
+ val p = Promise[T]
- g onSuccess {
- case x => p.trySuccess(x)
- }
+ f.foreach { x =>
+ p.trySuccess(x)
+ }
- p.future
- }
+ g.foreach { x =>
+ p.trySuccess(x)
+ }
+
+ p.future
+}
+```
+
+{% endtab %}
+{% tab 'Scala 3' for=futures-16 %}
+
+```scala
+def first[T](f: Future[T], g: Future[T]): Future[T] =
+ val p = Promise[T]
+
+ f.foreach { x =>
+ p.trySuccess(x)
+ }
+
+ g.foreach { x =>
+ p.trySuccess(x)
+ }
+
+ p.future
+```
-注意,在这种实现方式中,如果f与g都不是成功的,那么`first(f, g)`将不会实现(即返回一个值或者返回一个异常)。
+{% endtab %}
+{% endtabs %}
+
+注意,在这种实现方式中,如果 `f` 和 `g` 都不成功,那么 `first(f, g)` 将不会完成(即返回一个值或者返回一个异常)。
+
+
## 工具(Utilities)
-为了简化在并发应用中处理时序(time)的问题,`scala.concurrent`引入了Duration抽象。Duration不是被作为另外一个通常的时间抽象存在的。他是为了用在并发(concurrency)库中使用的,Duration位于`scala.concurrent`包中。
+为了简化在并发应用中处理时序(time)的问题, `scala.concurrent` 引入了 `Duration` 抽象。`Duration` 不是被作为另外一个通常的时间抽象存在的。他是为了用在并发(concurrency)库中使用的,`Duration` 位于 `scala.concurrent` 包中。
+
+`Duration` 是表示时间长短的基础类,其可以是有限的或者无限的。有限的duration用 `FiniteDuration` 类来表示,并通过 `Long` 长度和 `java.util.concurrent.TimeUnit` 来构造。无限的 durations,同样扩展自 `Duration`,只在两种情况下存在,`Duration.Inf` 和 `Duration.MinusInf`。库中同样提供了一些 `Duration` 的子类用来做隐式的转换,这些子类不应被直接使用。
+
+抽象的 `Duration` 类包含了如下方法:
+
+1. 到不同时间单位的转换(`toNanos`,`toMicros`,`toMillis`,`toSeconds`,`toMinutes`,`toHours`,`toDays` 和 `toUnit(unit: TimeUnit)`)。
+2. durations的比较(`<`,`<=`,`>`和`>=`)。
+3. 算术运算符(`+`,`-`,`*`,`/`和 `unary_-`)
+4. `this` duration 与提供的参数之间的最大和最小的方法(`min`,`max`)。
+5. 检查 duration是否有限(`isFinite`)。
+
+`Duration` 能够用如下方法实例化(`instantiated`):
+
+1. 通过 `Int` 和 `Long` 类型隐式转换,例如:`val d = 100 millis`。
+2. 通过传递一个 `Long` 长度和 `java.util.concurrent.TimeUnit`。例如:`val d = Duration(100, MILLISECONDS)`。
+3. 通过传递一个字符串来表示时间区间,例如: `val d = Duration("1.2 µs")`。
+
+Duration 也提供了 `unapply` 方法,因此可以被用于模式匹配中,例如:
+
+{% tabs futures-17 class=tabs-scala-version %}
+{% tab 'Scala 2' for=futures-17 %}
+
+```scala
+import scala.concurrent.duration._
+import java.util.concurrent.TimeUnit._
-Duration是表示时间长短的基础类,其可以是有限的或者无限的。有限的duration用FiniteDuration类来表示,并通过时间长度`(length)`和`java.util.concurrent.TimeUnit`来构造。无限的durations,同样扩展了Duration,只在两种情况下存在,`Duration.Inf`和`Duration.MinusInf`。库中同样提供了一些Durations的子类用来做隐式的转换,这些子类不应被直接使用。
+// instantiation
+val d1 = Duration(100, MILLISECONDS) // from Long and TimeUnit
+val d2 = Duration(100, "millis") // from Long and String
+val d3 = 100 millis // implicitly from Long, Int or Double
+val d4 = Duration("1.2 µs") // from String
-抽象的Duration类包含了如下方法:
+// pattern matching
+val Duration(length, unit) = 5 millis
+```
-到不同时间单位的转换`(toNanos, toMicros, toMillis, toSeconds, toMinutes, toHours, toDays and toUnit(unit: TimeUnit))`。
-durations的比较`(<,<=,>和>=)`。
-算术运算符`(+, -, *, / 和单值运算_-)`
-duration的最大最小方法`(min,max)`。
-测试duration是否是无限的方法`(isFinite)`。
-Duration能够用如下方法实例化`(instantiated)`:
+{% endtab %}
+{% tab 'Scala 3' for=futures-17 %}
-隐式的通过Int和Long类型转换得来 `val d = 100 millis`。
-通过传递一个`Long length`和`java.util.concurrent.TimeUnit`。例如`val d = Duration(100, MILLISECONDS)`。
-通过传递一个字符串来表示时间区间,例如 `val d = Duration("1.2 µs")`。
-Duration也提供了unapply方法,因此可以i被用于模式匹配中,例如:
+```scala
+import scala.concurrent.duration.*
+import java.util.concurrent.TimeUnit.*
- import scala.concurrent.duration._
- import java.util.concurrent.TimeUnit._
+// instantiation
+val d1 = Duration(100, MILLISECONDS) // from Long and TimeUnit
+val d2 = Duration(100, "millis") // from Long and String
+val d3 = 100.millis // implicitly from Long, Int or Double
+val d4 = Duration("1.2 µs") // from String
- // instantiation
- val d1 = Duration(100, MILLISECONDS) // from Long and TimeUnit
- val d2 = Duration(100, "millis") // from Long and String
- val d3 = 100 millis // implicitly from Long, Int or Double
- val d4 = Duration("1.2 µs") // from String
+// pattern matching
+val Duration(length, unit) = 5.millis
+```
- // pattern matching
- val Duration(length, unit) = 5 millis
+{% endtab %}
+{% endtabs %}
diff --git a/_zh-cn/overviews/scala3-book/ca-context-bounds.md b/_zh-cn/overviews/scala3-book/ca-context-bounds.md
index 56d948bad0..c7b81b6d12 100644
--- a/_zh-cn/overviews/scala3-book/ca-context-bounds.md
+++ b/_zh-cn/overviews/scala3-book/ca-context-bounds.md
@@ -4,7 +4,7 @@ type: section
description: This page demonstrates Context Bounds in Scala 3.
language: zh-cn
num: 61
-previous-page: ca-given-using-clauses
+previous-page: ca-context-parameters
next-page: ca-given-imports
partof: scala3-book
diff --git a/_zh-cn/overviews/scala3-book/ca-given-using-clauses.md b/_zh-cn/overviews/scala3-book/ca-context-parameters.md
similarity index 100%
rename from _zh-cn/overviews/scala3-book/ca-given-using-clauses.md
rename to _zh-cn/overviews/scala3-book/ca-context-parameters.md
diff --git a/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md b/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md
index 80136106d6..d8f0f1a4d6 100644
--- a/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md
+++ b/_zh-cn/overviews/scala3-book/ca-contextual-abstractions-intro.md
@@ -5,7 +5,7 @@ description: This chapter provides an introduction to the Scala 3 concept of Con
language: zh-cn
num: 58
previous-page: types-others
-next-page: ca-given-using-clauses
+next-page: ca-extension-methods
partof: scala3-book
overview-name: "Scala 3 — Book"
@@ -78,7 +78,7 @@ Scala 3 中的这些更改实现了术语推理与语言其余部分的更好分
本章将在以下各节中介绍其中的许多新功能。
-[givens]: {% link _zh-cn/overviews/scala3-book/ca-given-using-clauses.md %}
+[givens]: {% link _zh-cn/overviews/scala3-book/ca-context-parameters.md %}
[given-imports]: {% link _zh-cn/overviews/scala3-book/ca-given-imports.md %}
[implicit-conversions]: {% link _zh-cn/overviews/scala3-book/ca-implicit-conversions.md %}
[extension-methods]: {% link _zh-cn/overviews/scala3-book/ca-extension-methods.md %}
diff --git a/_zh-cn/overviews/scala3-book/ca-extension-methods.md b/_zh-cn/overviews/scala3-book/ca-extension-methods.md
index 7d780cae1f..2a8d4588ea 100644
--- a/_zh-cn/overviews/scala3-book/ca-extension-methods.md
+++ b/_zh-cn/overviews/scala3-book/ca-extension-methods.md
@@ -5,7 +5,7 @@ description: This page demonstrates how Extension Methods work in Scala 3.
language: zh-cn
num: 59
previous-page: ca-contextual-abstractions-intro
-next-page: ca-given-using-clauses
+next-page: ca-context-parameters
partof: scala3-book
overview-name: "Scala 3 — Book"
diff --git a/_zh-cn/overviews/scala3-book/collections-classes.md b/_zh-cn/overviews/scala3-book/collections-classes.md
index 307395fba9..c102860b53 100644
--- a/_zh-cn/overviews/scala3-book/collections-classes.md
+++ b/_zh-cn/overviews/scala3-book/collections-classes.md
@@ -119,6 +119,8 @@ NOTE: those images come from this page: https://docs.scala-lang.org/overviews/co
这是创建初始“列表”的方式:
+{% tabs list-creation %}
+{% tab 'Scala 2 and 3' %}
```scala
val ints = List(1, 2, 3)
val names = List("Joel", "Chris", "Ed")
@@ -126,19 +128,36 @@ val names = List("Joel", "Chris", "Ed")
// another way to construct a List
val namesAgain = "Joel" :: "Chris" :: "Ed" :: Nil
```
+{% endtab %}
+{% endtabs %}
如果您愿意,也可以声明 `List` 的类型,但通常不是必需的:
+{% tabs list-type %}
+{% tab 'Scala 2 and 3' %}
```scala
val ints: List[Int] = List(1, 2, 3)
val names: List[String] = List("Joel", "Chris", "Ed")
```
+{% endtab %}
+{% endtabs %}
一个例外是集合中有混合类型时。在这种情况下,您可能需要明确指定其类型:
+{% tabs list-mixed-types class=tabs-scala-version %}
+{% tab 'Scala 2' %}
```scala
val things: List[Any] = List(1, "two", 3.0)
```
+{% endtab %}
+
+{% tab 'Scala 3' %}
+```scala
+val things: List[String | Int | Double] = List(1, "two", 3.0) // with union types
+val thingsAny: List[Any] = List(1, "two", 3.0) // with any
+```
+{% endtab %}
+{% endtabs %}
### 将元素添加到列表
@@ -146,16 +165,24 @@ val things: List[Any] = List(1, "two", 3.0)
相反,您可以通过将元素添加到现有 `List` 来创建新列表。
例如,给定这个 `List`:
+{% tabs adding-elements-init %}
+{% tab 'Scala 2 and 3' %}
```scala
val a = List(1, 2, 3)
```
+{% endtab %}
+{% endtabs %}
使用 `List` 时,用 `::` 来_附加_一个元素,用 `:::` 把另一个 `List` 插在这个 `List` 之前,如下所示:
+{% tabs adding-elements-example %}
+{% tab 'Scala 2 and 3' %}
```scala
val b = 0 :: a // List(0, 1, 2, 3)
val c = List(-1, 0) ::: a // List(-1, 0, 1, 2, 3)
```
+{% endtab %}
+{% endtabs %}
你也可以在 `List` 中添加元素,但是因为 `List` 是一个单链表,你通常应该只在它前面添加元素;
在它的后面添加元素是一个相对较慢的操作,尤其是在处理大型序列时。
@@ -170,15 +197,23 @@ val c = List(-1, 0) ::: a // List(-1, 0, 1, 2, 3)
现在 IDE 为我们提供了极大的帮助,但是记住这些方法名称的一种方法是,认为 `:` 字符代表序列所在的一侧,因此当您使用 `+:` 时,您知道列表需要在右边,像这样:
+{% tabs list-prepending %}
+{% tab 'Scala 2 and 3' %}
```scala
0 +: a
```
+{% endtab %}
+{% endtabs %}
同样,当您使用 `:+` 时,您知道列表需要在左侧:
+{% tabs list-appending %}
+{% tab 'Scala 2 and 3' %}
```scala
a :+ 4
```
+{% endtab %}
+{% endtabs %}
有更多的技术方法可以考虑这一点,但这可能是记住方法名称的有用方法。
@@ -194,24 +229,51 @@ LATER: Add a discussion of `:` on method names, right-associativity, and infix o
给定一个名称 `List`:
+{% tabs list-loop-init %}
+{% tab 'Scala 2 and 3' %}
```scala
val names = List("Joel", "Chris", "Ed")
```
+{% endtab %}
+{% endtabs %}
您可以像这样打印每个字符串:
+{% tabs list-loop-example class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+for (name <- names) println(name)
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
```scala
for name <- names do println(name)
```
+{% endtab %}
+{% endtabs %}
这是它在 REPL 中的样子:
+{% tabs list-loop-repl class=tabs-scala-version %}
+{% tab 'Scala 2' %}
+```scala
+scala> for (name <- names) println(name)
+Joel
+Chris
+Ed
+```
+{% endtab %}
+
+{% tab 'Scala 3' %}
```scala
scala> for name <-names do println(name)
Joel
Chris
Ed
```
+{% endtab %}
+{% endtabs %}
将 `for` 循环与集合一起使用的一个好处是 Scala 是一致的,并且相同的方法适用于所有序列,包括 `Array`、`ArrayBuffer`、`List`、`Seq`、`Vector`、`Map` ,`Set` 等。
@@ -220,22 +282,34 @@ Ed
对于那些对历史感兴趣的人,Scala `List` 类似于 [Lisp 编程语言](https://en.wikipedia.org/wiki/Lisp_(programming_language)) 中的 `List`,它是最初于 1958 年确定的。
实际上,除了像这样创建一个 `List` 之外:
+{% tabs list-history-init %}
+{% tab 'Scala 2 and 3' %}
```scala
val ints = List(1, 2, 3)
```
+{% endtab %}
+{% endtabs %}
您也可以通过这种方式创建完全相同的列表:
+{% tabs list-history-init2 %}
+{% tab 'Scala 2 and 3' %}
```scala
val list = 1 :: 2 :: 3 :: Nil
```
+{% endtab %}
+{% endtabs %}
REPL 展示了它是如何工作的:
+{% tabs list-history-repl %}
+{% tab 'Scala 2 and 3' %}
```scala
scala> val list = 1 :: 2 :: 3 :: Nil
list: List[Int] = List(1, 2, 3)
```
+{% endtab %}
+{% endtabs %}
这是因为 `List` 是一个以 `Nil` 元素结尾的单链表,而 `::` 是一个 `List` 方法,其工作方式类似于 Lisp 的“cons”运算符。
@@ -246,22 +320,30 @@ Scala 集合还包括一个 [LazyList](https://www.scala-lang.org/api/current/sc
你可以看到 REPL 中的 `LazyList` 有多懒惰:
+{% tabs lazylist-example %}
+{% tab 'Scala 2 and 3' %}
```scala
val x = LazyList.range(1, Int.MaxValue)
x.take(1) // LazyList()
x.take(5) // LazyList()
x.map(_ + 1) // LazyList()
```
+{% endtab %}
+{% endtabs %}
在所有这些例子中,什么都没有发生。
事实上,除非你强迫它发生,否则什么都不会发生,例如通过调用它的 `foreach` 方法:
-````
+{% tabs lazylist-evaluation-example %}
+{% tab 'Scala 2 and 3' %}
+```scala
scala> x.take(1).foreach(println)
1
-````
+```
+{% endtab %}
+{% endtabs %}
-有关严格和非严格的用途、好处和缺点的更多信息严格(惰性)集合,请参阅 [The Architecture of Scala 2.13's Collections][strict] 页面上的“严格”和“非严格”讨论。
+有关严格和非严格的用途、好处和缺点的更多信息严格(惰性)集合,请参阅 [Scala 2.13集合的架构][strict] 页面上的“严格”和“非严格”讨论。
+TASTyViz is a tool to inspect TASTy files visually.
+At the time of writing, it is still in the early stages of developement, therefore you can expect missing functionality and less-than-ideal user experience, but it could still prove useful when debugging.
+You can check it out [here](https://github.com/shardulc/tastyviz).
+
[benefits]: https://www.scala-lang.org/blog/2018/04/30/in-a-nutshell.html
[erasure]: https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#type-erasure
[binary]: {% link _overviews/tutorials/binary-compatibility-for-library-authors.md %}
diff --git a/scripts/run-mdoc.sh b/scripts/run-mdoc.sh
index 32e7d23d2c..a4f4acbe2a 100755
--- a/scripts/run-mdoc.sh
+++ b/scripts/run-mdoc.sh
@@ -1,10 +1,15 @@
#!/bin/bash
set -eux
-cs launch org.scalameta:mdoc_2.13:2.3.3 -- \
+cs launch --scala-version 2.13.10 org.scalameta::mdoc:2.3.3 -- \
--in . \
--out /tmp/mdoc-out/ \
- --classpath $(cs fetch -p com.chuusai:shapeless_2.13:2.3.10) \
+ --classpath \
+ $(cs fetch --scala-version 2.13.10 -p \
+ com.chuusai::shapeless:2.3.10 \
+ org.scala-lang::toolkit:0.1.7 \
+ org.scala-lang::toolkit-test:0.1.7 \
+ ) \
--scalac-options "-Xfatal-warnings -feature" \
--no-link-hygiene \
--include '**.md'