Control Structures

Blocks

A block is a sequence of semi-colon-separated expressions surrounded by curly braces. For example, a block might be the body of a function:

def square10(n: Int) = {val x = 10; def square(x: Double) = x * x; square(x + n)}

scala> square10(3)
res23: Double = 169.0

The scope of all declarations inside of a block extends to the end of the block. In the example above square and x disappear as soon as the block terminates:

scala> square(x)
<console>:8: error: not found: value square
              square(x)
              ^
<console>:8: error: not found: value x
              square(x)

                     ^

A block is an expression. Its value is the value of its last sub-expression:

scala> val n = {1; 2; 3; 4; 5}
// lots of warnings
scala> n
5

Semicolons aren't necessary for multi-line blocks:

def square10(n: Int) = {
   val x = 10
   def square(x: Double) = x * x
   square(x + n)
}

Beginning with Scala 3.0 curly braces aren't needed for a block that's the body of a function or nested in another control structure. Instead indentation tells the parser the scope of the expression:

def square10(n: Int) =
   val x = 10
   def square(x: Double) = x * x
   square(x + n)

Conditional Expressions

Scala conditional expressions are similar to Java's x?y:z expressions:

scala> def max(x: Int, y: Int) = if x > y then x else y
max: (x: Int, y: Int)Int

scala> max(2, 3)
res26: Int = 3

Here's the equivalent Java version:

Integer max(Integer x, Integer y) {
   return (x > y)? x: y;
}

Note: Scala 2 syntax doesn't use the keyword "then". Instead the condition is surrounded by parentheses:

def max(x: Int, y: Int) = if (x > y) x else y

Scala's Match/Case

Scala's match expression is a powerful improvement to Java's switch statement.

Format:

key match {
   case guard1 => branch1
   case guard2 => branch2
   case guard3 => branch3
   // etc
   case _ => default
}

Note that the curly braces are optional in Scala 3.0.

Matching values:

var day = 3
day match
   case 0 => println("Sunday")
   case 1 => println("Monday")
   case 2 => println("Tueaday")
   case 3 => println("Wednesday")
   case 4 => println("Thursday")
   case 5 => println("Friday")
   case 6 => println("Saturday")
   case _ => throw Exception("Invalid day")

day match
   case 0 | 6 => println("Weekend")
   case 1 | 2 | 3 | 4 | 5 => println("Weekday")

Matching types:

Assume Rectangle and Circle are subclasses of Shape where Rectangle has a perimeter method and Circle has a circumference method:

abstract class Shape

class Circle extends Shape:
  var radius = 10.0
  def circumference = 2 * math.Pi * radius

class Rectangle extends Shape:
  var height = 10.0
  var length = 10.0
  def perimeter = 2 * height + 2 * length

We can define a general function for computing the border length of a shape as follows:

object shapeDemo extends App {

  def borderLength(s: Shape) =
    s match
      case r: Rectangle => r.perimeter
      case c: Circle => c.circumference
      case _ => 0.0
   
  println(boundaryLength(Circle()))     // prints 62.83185307179586
  println(boundaryLength(Rectangle()))  // prints 40.0

}

The equivalent in Java might look like this:

Double borderLength(Shape s) {
   if (s instanceof Rectangle) return ((Rectangle)s).perimeter();
   if (s instanceof Circle) return ((Circle)s).circumference();
   return 0
}

Notice that in the Java version we must perform safe casts, but Scala's match expression does this for us.

Matching Conditions

Of course match/case can be used on ordinary conditions, although the syntax is a bit wonky:

income match 
   case income if income < 100 => .1 * income
   case income if income < 1000 => .2 * income
   case income if income < 10000 => .3 * income
   case _ => .5 * income

Matching patterns

We can use a regular expression to extract matching elements from a string. (More on this later.)

val expPattern = """([0-9]+)\s*(\+|\*|-|/)\s*([0-9]+)""".r

def eval(exp: String) =  
    exp match {
       case expPattern(arg1, "+", arg2) => arg1.toInt + arg2.toInt
       case expPattern(arg1, "-", arg2) => arg1.toInt - arg2.toInt
       case expPattern(arg1, "*", arg2) => arg1.toInt * arg2.toInt
       case expPattern(arg1, "/", arg2) => arg1.toInt / arg2.toInt
       case _ => throw Exception("Invalid expression")
    }

Iterative Expressions

Scala has the same while and do loops as Java:

while (condition) expression

do expression while (condition)

The for loop is like Java's enhanced for loop in that it iterates over collections. The simplest type of collection is called a range.

Here are a few examples:

scala> for (i <- 0 to 9) print(i)
0123456789

scala> for (i <- 0 until 9) print(i)
012345678

One for-loop can iterate over multiple ranges simultaneously:

scala> for(i <- 0 to 3; j <- 0 to 3) println(i + j)
0
1
2
3
1
2
3
4
2
3
4
5
3
4
5
6

The for loop can have multiple generators and guards:

scala> for(i <- 0 to 15 if i % 3 == 0) print(" " + i)
 0 3 6 9 12 15

scala> for(i <- 0 until 5; j <- 0 until 5 if i != j) { println("" + i + " + " + j + " = " + (i + j)); }
0 + 1 = 1
0 + 2 = 2
0 + 3 = 3
0 + 4 = 4
1 + 0 = 1
1 + 2 = 3
1 + 3 = 4
1 + 4 = 5
2 + 0 = 2
2 + 1 = 3
2 + 3 = 5
2 + 4 = 6
3 + 0 = 3
3 + 1 = 4
3 + 2 = 5
3 + 4 = 7
4 + 0 = 4
4 + 1 = 5
4 + 2 = 6
4 + 3 = 7

For-loops can iterate over other types of collections:

scala> val greeting = "bon jour"
greeting: String = bon jour
scala> for(c <- greeting) print(c)
bon jour

Of course this works, too:

cala> for(i <- 0 until greeting.length) print(greeting(i))
bon jour

Comprehension can collect the values of each iteration into a collection:

scala> for(i <- 2 until 10) yield 3 * i + 2
res3: = Vector(8, 11, 14, 17, 20, 23, 26, 29)