Skip to content
2 changes: 1 addition & 1 deletion _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
38 changes: 38 additions & 0 deletions _overviews/scala3-book/collections-classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
84 changes: 84 additions & 0 deletions _overviews/scala3-book/control-structures.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

<blockquote class="help-info">
<i class="fa fa-info"></i>&nbsp;&nbsp;This feature was introduced in Scala 3.3.
</blockquote>

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
197 changes: 195 additions & 2 deletions _overviews/scala3-book/string-interpolation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 %}
Loading