Skip to content

Commit 35a66ca

Browse files
committed
Describe boundary-loop and add a few examples of custom control structures
1 parent 15b3f6f commit 35a66ca

File tree

1 file changed

+176
-1
lines changed

1 file changed

+176
-1
lines changed

_overviews/scala3-book/control-structures.md

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ Scala has the control structures you expect to find in a programming language, i
1616
- `while` loops
1717
- `try`/`catch`/`finally`
1818

19-
It also has two other powerful constructs that you may not have seen before, depending on your programming background:
19+
It also has three other powerful constructs that you may not have seen before, depending on your programming background:
2020

2121
- `for` expressions (also known as _`for` comprehensions_)
2222
- `match` expressions
23+
- `boundary`/`break` expressions
2324

2425
These are all demonstrated in the following sections.
2526

@@ -989,4 +990,178 @@ finally
989990

990991
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.
991992

993+
## boundary/break
994+
995+
The `boundary`/`break` expression is used to exit a block of code while optionally returning a value.
996+
For example, it can be used to break out of a nested loop:
997+
998+
{% tabs control-structures-32 %}
999+
{% tab 'Scala 3 Only' %}
1000+
1001+
```scala
1002+
import scala.util.boundary, boundary.break
1003+
1004+
val result =
1005+
boundary:
1006+
for i <- 1 to 10 do
1007+
for j <- 1 to 10 do
1008+
if i == 7 && j == 9 then break("found")
1009+
"not found"
1010+
1011+
println(result)
1012+
```
1013+
1014+
{% endtab %}
1015+
{% endtabs %}
1016+
1017+
Since `break` uses an exception to control the execution flow, mixing it with `try`/`catch` can lead to unexpected results:
1018+
1019+
{% tabs control-structures-33 %}
1020+
{% tab 'Scala 3 Only' %}
1021+
1022+
```scala
1023+
import scala.util.boundary, boundary.break
1024+
1025+
val result = boundary:
1026+
try
1027+
for i <- 1 to 10 do
1028+
some_operation(i)
1029+
if i == 5 then break(i)
1030+
catch
1031+
case e: Exception => -1
1032+
1033+
println(result) // prints -1, not 5
1034+
```
1035+
1036+
{% endtab %}
1037+
{% endtabs %}
1038+
1039+
The right way to use it would be to place the `boundary` expression inside the `try` block:
1040+
1041+
{% tabs control-structures-34 %}
1042+
{% tab 'Scala 3 Only' %}
1043+
1044+
```scala
1045+
import scala.util.boundary, boundary.break
1046+
1047+
val result =
1048+
try
1049+
boundary:
1050+
for i <- 1 to 10 do
1051+
some_operation(i)
1052+
if i == 5 then break(i)
1053+
catch
1054+
case e: Exception => -1
1055+
1056+
println(result) // 5
1057+
```
1058+
1059+
{% endtab %}
1060+
{% endtabs %}
1061+
1062+
## Custom control structures
1063+
1064+
The use of Scala 3 quiet syntax, by-name parameters, context parameters and `boundary`/`break` allows for writing code
1065+
that resembles the built-in control structures and helps with reducing the boilerplate.
1066+
In this example, the `repeat` function wraps a simple `for` loop, and the by-name `action` parameter will be executed `n`
1067+
times for its side effects.
1068+
1069+
{% tabs control-structures-35 %}
1070+
{% tab 'Scala 3 Only' %}
1071+
1072+
```scala
1073+
def repeat(n: Int)(action: => Unit): Unit =
1074+
for i <- 1 to n do
1075+
action
1076+
1077+
repeat(5):
1078+
println("test")
1079+
```
1080+
1081+
{% endtab %}
1082+
{% endtabs %}
1083+
1084+
Here's an example that simulates a `do`-`while` loop by using `boundary`/`break`:
1085+
1086+
{% tabs control-structures-36 %}
1087+
{% tab 'Scala 3 Only' %}
1088+
1089+
```scala
1090+
import scala.util.boundary, boundary.break, boundary.Label
1091+
1092+
def loop(action: Label[Unit] ?=> Unit): Unit =
1093+
boundary[Unit]:
1094+
while true do
1095+
action
1096+
1097+
def until(condition: => Boolean)(using Label[Unit]): Unit =
1098+
if condition then break()
1099+
```
1100+
1101+
{% endtab %}
1102+
{% endtabs %}
1103+
1104+
The `loop` function will continue to execute `action` indefinitely, unless the execution breaks out of the internal
1105+
`while` loop. The `until` function will stop the execution of `loop` if `condition` is met. `break()` needs a context parameter
1106+
of type `Label[Unit]`, denoting which `boundary` it is supposed to break to.
1107+
To use the `loop`/`until` structure, pass the code to be executed as an argument to `loop`, including the `until` check:
1108+
1109+
{% tabs control-structures-37 %}
1110+
{% tab 'Scala 3 Only' %}
1111+
1112+
```scala
1113+
var i = 0
1114+
loop:
1115+
println(i)
1116+
i = i + 1
1117+
until(i == 10)
1118+
```
1119+
1120+
{% endtab %}
1121+
{% endtabs %}
1122+
1123+
The following example uses a context parameter to store actions to be executed after a block of code is completed. The
1124+
`with_defer` function defines a scope in which `defer` can be used to defer execution of actions.
1125+
1126+
{% tabs control-structures-38 %}
1127+
{% tab 'Scala 3 Only' %}
1128+
1129+
```scala
1130+
import scala.collection.mutable.ArrayBuffer
1131+
1132+
class DeferredAction(action: => Unit):
1133+
def execute = action
1134+
1135+
def defer(action: => Unit)(using buffer: ArrayBuffer[DeferredAction]) =
1136+
buffer += new DeferredAction(action)
1137+
1138+
def with_defer(action: ArrayBuffer[DeferredAction] ?=> Unit) =
1139+
given deferedActions: ArrayBuffer[DeferredAction] = ArrayBuffer[DeferredAction]()
1140+
try
1141+
action
1142+
finally
1143+
deferedActions.foreach(_.execute)
1144+
```
1145+
1146+
{% endtab %}
1147+
{% endtabs %}
1148+
1149+
To use it, place the code inside the `with_defer` block. Bear in mind that there is no guarantee that a deferred action
1150+
actually executes, for example if another action throws an exception.
1151+
1152+
{% tabs control-structures-39 %}
1153+
{% tab 'Scala 3 Only' %}
1154+
1155+
```scala
1156+
with_defer:
1157+
defer:
1158+
println("this will be second")
1159+
defer:
1160+
println("this will be printed at the end")
1161+
println("this will be printed first")
1162+
```
1163+
1164+
{% endtab %}
1165+
{% endtabs %}
1166+
9921167
[matchable]: {{ site.scala3ref }}/other-new-features/matchable.html

0 commit comments

Comments
 (0)