This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 3.5, “ Scala: How to use break and continue in for loops (and while loops)”
Problem
You have a situation where you need to use a break
or continue
construct, but Scala doesn’t have break
or continue
keywords.
Solution
It’s true that Scala doesn’t have break
and continue
keywords, but it does offer similar functionality through scala.util.control.Breaks.
The following code demonstrates the Scala “break” and “continue” approach:
package com.alvinalexander.breakandcontinue import util.control.Breaks._ object BreakAndContinueDemo extends App { println("\n=== BREAK EXAMPLE ===") breakable { for (i <- 1 to 10) { println(i) if (i > 4) break // break out of the for loop } } println("\n=== CONTINUE EXAMPLE ===") val searchMe = "peter piper picked a peck of pickled peppers" var numPs = 0 for (i <- 0 until searchMe.length) { breakable { if (searchMe.charAt(i) != 'p') { break // break out of the 'breakable', continue the outside loop } else { numPs += 1 } } } println("Found " + numPs + " p's in the string.") }
Here’s the output from the code:
=== BREAK EXAMPLE === 1 2 3 4 5 === CONTINUE EXAMPLE === Found 9 p's in the string.
(The “pickled peppers” example comes from a continue example in the Java documentation. More on this at the end of the recipe.)
The following discussions describe how this code works.
The ‘break’ example
The break
example is pretty easy to reason about. Again, here’s the code:
breakable { for (i <- 1 to 10) { println(i) if (i > 4) break // break out of the for loop } }
In this case, when i
becomes greater than 4
, the break
“keyword” is reached. At this point an exception is thrown, and the for
loop is exited. The breakable
“keyword” essentially catches the exception, and the flow of control continues with any other code that might be after the breakable
block.
Note that break
and breakable
aren’t actually keywords; they’re methods in scala.util.control.Breaks. In Scala 2.10, the break
method is declared as follows to throw an instance of a BreakControl
exception when it’s called:
private val breakException = new BreakControl def break(): Nothing = { throw breakException }
The breakable
method is defined to catch a BreakControl
exception, like this:
def breakable(op: => Unit) { try { op } catch { case ex: BreakControl => if (ex ne breakException) throw ex } }
See Recipe 3.18 for examples of how to implement your own control structures in a manner similar to the Breaks
library.
The ‘continue’ example
Given the explanation for the break
example, you can now reason about how the “continue” example works. Here’s the code again:
val searchMe = "peter piper picked a peck of pickled peppers" var numPs = 0 for (i <- 0 until searchMe.length) { breakable { if (searchMe.charAt(i) != 'p') { break // break out of the 'breakable', continue the outside loop } else { numPs += 1 } } } println("Found " + numPs + " p's in the string.")
Following the earlier explanation, as the code walks through the characters in the String
variable named searchMe
, if the current character is not the letter p
, the code breaks out of the if/then statement, and the loop continues executing.
As before, what really happens is that the break
method is reached, an exception is thrown, and that exception is caught by breakable
. The exception serves to break out of the if/then statement, and catching it allows the for
loop to continue executing with the next element.
General syntax
The general syntax for implementing break and continue functionality is shown in the following examples, which are partially written in pseudocode, and compared to their Java equivalents..
To implement a break, this Scala:
breakable { for (x <- xs) { if (cond) break } }
corresponds to this Java:
for (X x : xs) { if (cond) break; }
To implement continue functionality, this Scala:
for (x <- xs) { breakable { if (cond) break } }
corresponds to this Java:
for (X x : xs) { if (cond) continue; }
About that ‘continue’ example...
The “continue” example shown is a variation of the Java continue example shown on the Oracle website. If you know Scala, you know that there are better ways to solve this particular problem. For instance, a direct approach is to use the count
method with a simple anonymous function:
val count = searchMe.count(_ == 'p')
When this code is run, count is again 9
.
Nested loops and labeled breaks
In some situations, you may need nested break
statements. Or, you may prefer labeled break
statements. In either case, you can create labeled breaks as shown in the following example:
package com.alvinalexander.labeledbreaks object LabeledBreakDemo extends App { import scala.util.control._ val Inner = new Breaks val Outer = new Breaks Outer.breakable { for (i <- 1 to 5) { Inner.breakable { for (j <- 'a' to 'e') { if (i == 1 && j == 'c') Inner.break else println(s"i: $i, j: $j") if (i == 2 && j == 'b') Outer.break } } } } }
In this example, if the first if
condition is met, an exception is thrown and caught by Inner.breakable
, and the outer for
loop continues. But if the second if
condition is triggered, control of flow is sent to Outer.breakable
, and both loops are exited. Running this object results in the following output:
i: 1, j: a i: 1, j: b i: 2, j: a
Use the same approach if you prefer labeled breaks. This example shows how you can use the same technique with just one break
method call:
import scala.util.control._ val Exit = new Breaks Exit.breakable { for (j <- 'a' to 'e') { if (j == 'c') Exit.break else println(s"j: $j") } }
Discussion
If you don’t like using break
and continue
, there are several other ways to attack these problems.
For instance, if you want to add monkeys to a barrel, but only until the barrel is full, you can use a simple boolean test to break out of a for
loop:
var barrelIsFull = false for (monkey <- monkeyCollection if !barrelIsFull) { addMonkeyToBarrel(monkey) barrelIsFull = checkIfBarrelIsFull }
Another approach is to place your algorithm inside a function, and then return from the function when the desired condition is reached. In the following example, the sumToMax
function returns early if sum
becomes greater than limit
:
// calculate a sum of numbers, but limit it to a 'max' value def sumToMax(arr: Array[Int], limit: Int): Int = { var sum = 0 for (i <- arr) { sum += i if (sum > limit) return limit } sum } val a = Array.range(0,10) println(sumToMax(a, 10))
A common approach in functional programming is to use recursive algorithms. This is demonstrated in a recursive approach to a factorial function, where the condition n == 1
results in a break from the recursion:
def factorial(n: Int): Int = { if (n == 1) 1 else n * factorial(n - 1) }
Note that this example does not use tail recursion and is therefore not an optimal approach, especially if the starting value n
is very large. A more optimal solution takes advantage of tail recursion:
import scala.annotation.tailrec def factorial(n: Int): Int = { @tailrec def factorialAcc(acc: Int, n: Int): Int = { if (n <= 1) acc else factorialAcc(n * acc, n - 1) } factorialAcc(1, n) }
Note that you can use the @tailrec
annotation in situations like this to confirm that your algorithm is tail recursive. If you use this annotation and your algorithm isn’t tail recursive, the compiler will complain. For instance, if you attempt to use this annotation on the first version of the factorial
method, you’ll get the following compile-time error:
Could not optimize @tailrec annotated method factorial: it contains a recursive call not in tail position
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |