diff --git a/_config.yml b/_config.yml index f9df6365b3..e2879b2dee 100644 --- a/_config.yml +++ b/_config.yml @@ -17,7 +17,7 @@ keywords: scala-version: 2.13.18 scala-212-version: 2.12.21 -scala-3-version: 3.8.0 +scala-3-version: 3.8.1 collections: style: diff --git a/_overviews/scala3-book/collections-classes.md b/_overviews/scala3-book/collections-classes.md index acf3a7ff87..052eb95537 100644 --- a/_overviews/scala3-book/collections-classes.md +++ b/_overviews/scala3-book/collections-classes.md @@ -486,6 +486,44 @@ Ed {% endtabs %} + +## Array + +Scala `Array` elements are mutable, indexed, and have a fixed size. + +### Creating an Array + +Create an `Array` with initial values like this: + +{% tabs array-creation %} + +{% tab 'Scala 2 and 3' %} +```scala +val a = Array(1, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +### Accessing and updating elements + +Access and update `Array` elements just like an `ArrayBuffer`: + +{% tabs array-update %} + +{% tab 'Scala 2 and 3' %} +```scala +val a = Array(1, 2, 3) +a(0) // 1 +a(0) = 10 // Array(10, 2, 3) +``` +{% endtab %} + +{% endtabs %} + +If you need a resizable sequence, consider using `ArrayBuffer` instead. + + ## ArrayBuffer Use `ArrayBuffer` when you need a general-purpose, mutable indexed sequence in your Scala applications. diff --git a/_overviews/scala3-book/control-structures.md b/_overviews/scala3-book/control-structures.md index 5e10cd7f76..d992e89fed 100644 --- a/_overviews/scala3-book/control-structures.md +++ b/_overviews/scala3-book/control-structures.md @@ -1094,4 +1094,88 @@ finally Assuming that the `openAndReadAFile` method uses the Java `java.io.*` classes to read a file and doesn't catch its exceptions, attempting to open and read a file can result in both a `FileNotFoundException` and an `IOException`, and those two exceptions are caught in the `catch` block of this example. +## Custom Control Structures + +Scala allows you to define your own control structures. You can create methods that can be used in a way similar to built-in control structures such as `if`/`then`/`else` or `while` loops. This is primarily achieved using _by-name parameters_. + +### Defining your own control structures + +A by-name parameter is specified by prepending `=>` to the type, like `body: => Unit`. Unlike a normal by-value parameter, a by-name parameter is not evaluated when the method is called. Instead, it is evaluated every time it is referenced within the method. + +This feature allows you to accept a block of code that is run on demand, which is essential for defining control logic. + +Here is an example of a simple `repeat` loop that runs a block of code a specified number of times: + +{% tabs custom-control-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=custom-control-1 %} +```scala +def repeat(n: Int)(body: => Unit): Unit = { + if (n > 0) { + body + repeat(n - 1)(body) + } +} + +// usage +repeat(3) { + println("Hello") +} +``` +{% endtab %} +{% tab 'Scala 3' for=custom-control-1 %} +```scala +def repeat(n: Int)(body: => Unit): Unit = + if n > 0 then + body + repeat(n - 1)(body) + +// usage +repeat(3) { + println("Hello") +} +``` +{% endtab %} +{% endtabs %} + +The `body` parameter is evaluated each time `body` is mentioned in the `repeat` method. Because `repeat` is defined with multiple parameter lists, you can use the block syntax `{ ... }` for the second argument, making it look like a language keyword. + +### New in Scala 3.3: boundary and break + +
+ This feature was introduced in Scala 3.3. ++ +Scala 3.3 introduces `boundary` and `break` in the `scala.util` package to provide a clearer, more structured way to handle non-local returns or to "break" out of nested loops and control structures. This replaces the older `scala.util.control.Breaks` and creating methods that throw exceptions for control flow. + +To use it, you define a `boundary` block. Inside that block, you can call `break` to effectively return a value from that block immediately. + +Here is an example of a method that searches for the first index of a target element in a list of integers: + +{% tabs custom-control-2 %} +{% tab 'Scala 3 Only' %} +```scala +import scala.util.boundary, boundary.break + +def firstIndex(xs: List[Int], target: Int): Int = + boundary: + for (x, i) <- xs.zipWithIndex do + if x == target then break(i) + -1 + +val xs = List(1, 2, 3, 4, 5) +val found = firstIndex(xs, 3) // 2 +val notFound = firstIndex(xs, 99) // -1 +``` +{% endtab %} +{% endtabs %} + +In this example: +1. `boundary` establishes a scope. +2. The `for` loop iterates through the list. +3. If `x == target`, `break(i)` is called. This immediately exits the `boundary` block and returns `i`. +4. If the loop finishes without breaking, the code after the loop (`-1`) is returned. + +This mechanism provides a structured alternative to using exceptions for control flow. Note that the compiler will optimize `boundary` and `break` to simple jumps if possible (for example, when the break doesn't happen within a nested method call), avoiding the overhead of exception handling. + + [matchable]: {{ site.scala3ref }}/other-new-features/matchable.html diff --git a/_overviews/scala3-book/string-interpolation.md b/_overviews/scala3-book/string-interpolation.md index c0c417fb87..886eb96e9d 100644 --- a/_overviews/scala3-book/string-interpolation.md +++ b/_overviews/scala3-book/string-interpolation.md @@ -244,7 +244,7 @@ id"string content" {% endtab %} {% endtabs %} -it transforms it into a method call (`id`) on an instance of [StringContext](https://www.scala-lang.org/api/current/scala/StringContext.html). +it transforms it into a method call (`id(...)`) on an instance of [StringContext](https://www.scala-lang.org/api/current/scala/StringContext.html). This method can also be available on implicit scope. To define our own string interpolation, we need to create an implicit class (Scala 2) or an `extension` method (Scala 3) that adds a new method to `StringContext`. @@ -317,7 +317,8 @@ As a result, each of the fragments of the processed String are exposed in the `StringContext.parts` member, while any expressions values in the string are passed in to the method's `args` parameter. -### Example Implementation + +#### Example Implementation A naive implementation of our Point interpolator method might look something like below, though a more sophisticated method may choose to have more precise control over the @@ -365,6 +366,198 @@ of custom interpolators as above can allow for powerful syntactic shorthand, and community has already made swift use of this syntax for things like ANSI terminal color expansion, executing SQL queries, magic `$"identifier"` representations, and many others. +### Pattern Matching + +It is also possible to use string interpolation in patterns, for both built-in and user-defined interpolators: +{% tabs example-pat-match class=tabs-scala-version %} + +{% tab 'Scala 2' for=example-pat-match %} +```scala +some_value match { + + // built-in interpolator: + case s"Hello, $name!" => // Executes for Strings which start with "Hello, " and end in "!" + + // hypothetical custom interpolator: + case p"$a, 0" => // Executes for Points whose second coordinate is 0 +} +``` +{% endtab %} + +{% tab 'Scala 3' for=example-pat-match %} +```scala +some_value match + + // built-in interpolator: + case s"Hello, $name!" => // Executes for Strings which start with "Hello, " and end in "!" + + // hypothetical custom interpolator: + case p"$a, 0" => // Executes for example for Points whose second coordinate is 0 +``` +{% endtab %} + +{% endtabs %} + +Note however there are not extractors by default for the `f` and `raw` interpolators, so neither `case f"..."` nor `case raw"..."` will work. +(Unless a library provided them.) + +Anytime the compiler encounters a processed string pattern of the form: + +{% tabs example-pattern %} +{% tab 'Scala 2 and 3' for=example-pattern %} +```scala +id"string content" +``` +{% endtab %} +{% endtabs %} + +it transforms it into a pattern (`id(...)`) on an instance of [StringContext](https://www.scala-lang.org/api/current/scala/StringContext.html). +To define our own string interpolation, we need to create an implicit class (Scala 2) or a `Conversion` instance (Scala 3) that adds an extractor member to `StringContext`. + +As an example, let's assume we have a `Point` class and want to create a custom pattern `p"$a,$b"` that extracts the coordinates of a `Point` object. + +{% tabs custom-interpolator-1-pattern %} +{% tab 'Scala 2 and 3' for=custom-interpolator-1-pattern %} +```scala +case class Point(x: Double, y: Double) + +val pt: Point = Point(1, 2) +pt match case p"$a,$b" => a + b // a = 1, b = 2 +``` +{% endtab %} +{% endtabs %} + +We'd create a custom extractor which extracts two coordinates, and make it act as a member `p` of `StringContext` with something like: + +{% tabs custom-interpolator-2-pattern class=tabs-scala-version %} + +{% tab 'Scala 2' for=custom-interpolator-2-pattern %} +```scala +implicit class PointHelper(val sc: StringContext) { + object p { + def unapply(point: Point): Option[(Double, Double)] = ??? + } +} +``` + +**Note:** This time it's not possible to extend `AnyVal` since we add an object to it. + +{% endtab %} + +{% tab 'Scala 3' for=custom-interpolator-2-pattern %} +```scala +class PointExtractor(sc: StringContext): + def unapply(point: Point): Option[(Double,Double)] = ??? + +extension (sc: StringContext) + def p = PointExtractor(sc) +``` + +{% endtab %} + +{% endtabs %} + +Once this extension is in scope and the Scala compiler encounters a pattern `p"some string"`, it +will process `some string` to extract String tokens which lie between embedded patterns in the string. + +For example, `point match case p"$a, $b"` would turn into: + +{% tabs extension-desugaring-pattern class=tabs-scala-version %} + +{% tab 'Scala 2' for=extension-desugaring-pattern %} +```scala +val someIdentifier = StringContext("",",","") +point match case someIdentifier.p(a, b) +``` + +Where `someIdentifier` is an identifier guaranteed not to clash with anything in scope, and thus cannot be interracted with. + +The implicit class is then used to rewrite it to the following: + +```scala +val someIdentifier = PointHelper(StringContext("",",","")) +point match case someIdentifier.p(a, b) +``` +{% endtab %} + +{% tab 'Scala 3' for=extension-desugaring-pattern %} +```scala +val someIdentifier = StringContext("",",","") +point match case someIdentifier.p(a, b) +``` + +Where `someIdentifier` is an identifier guaranteed not to clash with anything in scope, and thus cannot be interracted with. +And `p` is provided by our extension methods. +{% endtab %} + +{% endtabs %} + +As a result, each of the fragments of the processed String are exposed in the +`StringContext.parts` member, while the extractor `p` is used to extract the variables. + +#### Example Implementation + +An implementation of our Point string interpolator extractor might look something like below, +though a more sophisticated implementation may choose to handle more pattern diversitiy (for example `p"0, $b"`) +and/or stronger exhaustiveness guarantess. + +{% tabs implementation-pattern class=tabs-scala-version %} + +{% tab 'Scala 2' for=implementation-pattern %} +```scala +import scala.language.implicitConversions + +case class Point(x: Double, y: Double) + +implicit class PointHelper(val sc: StringContext) { + object p { + def unapply(point: Point): Option[(Double,Double)] = { + sc.parts match { + + // if the pattern is p"$a,$b" or p"$a, $b" return the elements + case Seq("", "," | ", ", "") => + Some((point.x, point.y)) + + case _ => + throw IllegalArgumentException("The pattern is not well-formed") + } + } + } +} + + +Point(2, 3) match { +// case p"$x$y" => x + y // IllegalArgumentException: The pattern is not well-formed + case p"$x,$y" => x + y +} +``` +{% endtab %} + +{% tab 'Scala 3' for=implementation-pattern %} +```scala +case class Point(x: Double, y: Double) + +extension (sc: StringContext) + def p = PointExtractor(sc) + +class PointExtractor(sc: StringContext): + def unapply(point: Point): Option[(Double,Double)] = + sc.parts match + + // checks if the pattern is p"$a,$b" or p"$a, $b" + case Seq("", "," | ", ", "") => + Some((point.x, point.y)) + + case _ => + throw IllegalArgumentException("The pattern was not well-formed") + +Point(2, 3) match +// case p"$x$y" => x + y // IllegalArgumentException: The pattern was not well-formed + case p"$x,$y" => x + y +``` +{% endtab %} +{% endtabs %} + [java-format-docs]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html#detail [value-class]: {% link _overviews/core/value-classes.md %} [sip-11]: {% link _sips/sips/011-string-interpolation.md %} diff --git a/_overviews/scaladoc/for-library-authors.md b/_overviews/scaladoc/for-library-authors.md index 621861450e..48a3703bcb 100644 --- a/_overviews/scaladoc/for-library-authors.md +++ b/_overviews/scaladoc/for-library-authors.md @@ -56,32 +56,33 @@ Scaladoc comments can go before fields, methods, classes, traits, objects and even (especially) package objects. Scaladoc comments for package objects make a great place to put an overview of a specific package or API. -For class *primary constructors* which in Scala coincide with the definition +For class _primary constructors_ which in Scala coincide with the definition of the class itself, a `@constructor` tag is used to target a comment to be put on the primary constructor documentation rather than the class overview. ## Tags + Scaladoc uses `@` tags to provide specific detail fields in the comments. These include: - ### Class specific tags -- `@constructor` placed in the class comment will describe the primary constructor. +- `@constructor` placed in the class comment will describe the primary constructor. ### Method specific tags -- `@return` detail the return value from a method (one per method). +- `@return` detail the return value from a method (one per method). ### Method, Constructor and/or Class tags + - `@throws` what exceptions (if any) the method or constructor may throw. - `@param` detail a value parameter for a method or constructor, provide one per parameter to the method/constructor. - `@tparam` detail a type parameter for a method, constructor or class. Provide one per type parameter. - ### Usage tags + - `@see` reference other sources of information like external document links or related entities in the documentation. - `@note` add a note for pre- or post-conditions, or any other notable restrictions @@ -91,16 +92,16 @@ include: definition is too complex or noisy. An example is (in the collections API), providing documentation for methods that omit the implicit `canBuildFrom`. - ### Member grouping tags These tags are well-suited to larger types or packages, with many members. They allow you to organize the Scaladoc page into distinct sections, with each one shown separately, in the order that you choose. -These tags are *not* enabled by default! You must pass the `-groups` +These tags are _not_ enabled by default! You must pass the `-groups` flag to Scaladoc in order to turn them on. Typically, the sbt for this will look something like: + ``` scalacOptions in (Compile, doc) ++= Seq( "-groups" @@ -129,20 +130,21 @@ the resulting documentation. an implicit priority of 1000. Use a value between 0 and 999 to set a relative position to other groups. Low values will appear before high values. - ### Diagram tags + - `@contentDiagram` - use with traits and classes to include a content hierarchy diagram showing included types. - The diagram content can be fine-tuned with additional specifiers taken from `hideNodes`, `hideOutgoingImplicits`, - `hideSubclasses`, `hideEdges`, `hideIncomingImplicits`, `hideSuperclasses` and `hideInheritedNode`. - `hideDiagram` can be supplied to prevent a diagram from being created if it would be created by default. Packages - and objects have content diagrams by default. + The diagram content can be fine-tuned with additional specifiers taken from `hideNodes`, `hideOutgoingImplicits`, + `hideSubclasses`, `hideEdges`, `hideIncomingImplicits`, `hideSuperclasses` and `hideInheritedNode`. + `hideDiagram` can be supplied to prevent a diagram from being created if it would be created by default. Packages + and objects have content diagrams by default. - `@inheritanceDiagram` - TODO ### Other tags + - `@author` provide author information for the following entity - `@version` the version of the system or API that this entity is a part of. - `@since` like `@version` but defines the system or API that this entity was - *first* defined in. + _first_ defined in. - `@todo` for documenting unimplemented features or unimplemented aspects of an entity. - `@deprecated` marks the entity as deprecated, **providing both** the @@ -152,20 +154,21 @@ the resulting documentation. provided locally. - `@documentable` Expand a type alias and abstract type into a full template page. - TODO: Test the "abstract type" claim - no examples of this in the Scala code base - ### Macros + - `@define