Scala has the control structures you expect to find in a programming language, including:
if
/then
/else
for
loopswhile
loopstry
/catch
/finally
It also has two other powerful constructs that you may not have seen before, depending on your programming background:
for
expressions (also known asfor
comprehensions)match
expressions
These are all demonstrated in the following sections.
The if/then/else construct
A one-line Scala if
statement looks like this:
if (x == 1) println(x)
if x == 1 then println(x)
When you need to run multiple lines of code after an if
equality comparison, use this syntax:
if (x == 1) {
println("x is 1, as you can see:")
println(x)
}
if x == 1 then
println("x is 1, as you can see:")
println(x)
The if
/else
syntax looks like this:
if (x == 1) {
println("x is 1, as you can see:")
println(x)
} else {
println("x was not 1")
}
if x == 1 then
println("x is 1, as you can see:")
println(x)
else
println("x was not 1")
And this is the if
/else if
/else
syntax:
if (x < 0)
println("negative")
else if (x == 0)
println("zero")
else
println("positive")
if x < 0 then
println("negative")
else if x == 0 then
println("zero")
else
println("positive")
end if
statement
This is new in Scala 3, and not supported in Scala 2.
You can optionally include an end if
statement at the end of each expression, if you prefer:
if x == 1 then
println("x is 1, as you can see:")
println(x)
end if
if
/else
expressions always return a result
Note that if
/else
comparisons form expressions, meaning that they return a value which you can assign to a variable.
Because of this, there’s no need for a special ternary operator:
val minValue = if (a < b) a else b
val minValue = if a < b then a else b
Because they return a value, you can use if
/else
expressions as the body of a method:
def compare(a: Int, b: Int): Int =
if (a < b)
-1
else if (a == b)
0
else
1
def compare(a: Int, b: Int): Int =
if a < b then
-1
else if a == b then
0
else
1
Aside: Expression-oriented programming
As a brief note about programming in general, when every expression you write returns a value, that style is referred to as expression-oriented programming, or EOP. For example, this is an expression:
val minValue = if (a < b) a else b
val minValue = if a < b then a else b
Conversely, lines of code that don’t return values are called statements, and they’re used for their side-effects. For example, these lines of code don’t return values, so they’re used for their side effects:
if (a == b) action()
println("Hello")
if a == b then action()
println("Hello")
The first example runs the action
method as a side effect when a
is equal to b
.
The second example is used for the side effect of printing a string to STDOUT.
As you learn more about Scala you’ll find yourself writing more expressions and fewer statements.
for
loops
In its most simple use, a Scala for
loop can be used to iterate over the elements in a collection.
For example, given a sequence of integers, you can loop over its elements and print their values like this:
val ints = Seq(1, 2, 3)
for (i <- ints) println(i)
val ints = Seq(1, 2, 3)
for i <- ints do println(i)
The code i <- ints
is referred to as a generator. In any generator p <- e
, the expression e
can generate zero or many bindings to the pattern p
.
This is what the result looks like in the Scala REPL:
scala> val ints = Seq(1,2,3)
ints: Seq[Int] = List(1, 2, 3)
scala> for (i <- ints) println(i)
1
2
3
scala> val ints = Seq(1,2,3)
ints: Seq[Int] = List(1, 2, 3)
scala> for i <- ints do println(i)
1
2
3
When you need a multiline block of code following the for
generator, use the following syntax:
for (i <- ints) {
val x = i * 2
println(s"i = $i, x = $x")
}
for i <- ints
do
val x = i * 2
println(s"i = $i, x = $x")
Multiple generators
for
loops can have multiple generators, as shown in this example:
for {
i <- 1 to 2
j <- 'a' to 'b'
k <- 1 to 10 by 5
} {
println(s"i = $i, j = $j, k = $k")
}
for
i <- 1 to 2
j <- 'a' to 'b'
k <- 1 to 10 by 5
do
println(s"i = $i, j = $j, k = $k")
That expression prints this output:
i = 1, j = a, k = 1
i = 1, j = a, k = 6
i = 1, j = b, k = 1
i = 1, j = b, k = 6
i = 2, j = a, k = 1
i = 2, j = a, k = 6
i = 2, j = b, k = 1
i = 2, j = b, k = 6
Guards
for
loops can also contain if
statements, which are known as guards:
for {
i <- 1 to 5
if i % 2 == 0
} {
println(i)
}
for
i <- 1 to 5
if i % 2 == 0
do
println(i)
The output of that loop is:
2
4
A for
loop can have as many guards as needed.
This example shows one way to print the number 4
:
for {
i <- 1 to 10
if i > 3
if i < 6
if i % 2 == 0
} {
println(i)
}
for
i <- 1 to 10
if i > 3
if i < 6
if i % 2 == 0
do
println(i)
Using for
with Maps
You can also use for
loops with a Map
.
For example, given this Map
of state abbreviations and their full names:
val states = Map(
"AK" -> "Alaska",
"AL" -> "Alabama",
"AR" -> "Arizona"
)
You can print the keys and values using for
, like this:
for ((abbrev, fullName) <- states) println(s"$abbrev: $fullName")
for (abbrev, fullName) <- states do println(s"$abbrev: $fullName")
Here’s what that looks like in the REPL:
scala> for ((abbrev, fullName) <- states) println(s"$abbrev: $fullName")
AK: Alaska
AL: Alabama
AR: Arizona
scala> for (abbrev, fullName) <- states do println(s"$abbrev: $fullName")
AK: Alaska
AL: Alabama
AR: Arizona
As the for
loop iterates over the map, each key/value pair is bound to the variables abbrev
and fullName
, which are in a tuple:
(abbrev, fullName) <- states
As the loop runs, the variable abbrev
is assigned to the current key in the map, and the variable fullName
is assigned to the current map value.
for
expressions
In the previous for
loop examples, those loops were all used for side effects, specifically to print those values to STDOUT using println
.
It’s important to know that you can also create for
expressions that return values.
You create a for
expression by adding the yield
keyword and an expression to return, like this:
val list =
for (i <- 10 to 12)
yield i * 2
// list: IndexedSeq[Int] = Vector(20, 22, 24)
val list =
for i <- 10 to 12
yield i * 2
// list: IndexedSeq[Int] = Vector(20, 22, 24)
After that for
expression runs, the variable list
is a Vector
that contains the values shown.
This is how the expression works:
- The
for
expression starts to iterate over the values in the range(10, 11, 12)
. It first works on the value10
, multiplies it by2
, then yields that result, the value20
. - Next, it works on the
11
—the second value in the range. It multiplies it by2
, then yields the value22
. You can think of these yielded values as accumulating in a temporary holding place. - Finally, the loop gets the number
12
from the range, multiplies it by2
, yielding the number24
. The loop completes at this point and yields the final result, theVector(20, 22, 24)
.
While the intent of this section is to demonstrate for
expressions, it can help to know that the for
expression shown is equivalent to this map
method call:
val list = (10 to 12).map(i => i * 2)
for
expressions can be used any time you need to traverse all the elements in a collection and apply an algorithm to those elements to create a new list.
Here’s an example that shows how to use a block of code after the yield
:
val names = List("_olivia", "_walter", "_peter")
val capNames = for (name <- names) yield {
val nameWithoutUnderscore = name.drop(1)
val capName = nameWithoutUnderscore.capitalize
capName
}
// capNames: List[String] = List(Olivia, Walter, Peter)
val names = List("_olivia", "_walter", "_peter")
val capNames = for name <- names yield
val nameWithoutUnderscore = name.drop(1)
val capName = nameWithoutUnderscore.capitalize
capName
// capNames: List[String] = List(Olivia, Walter, Peter)
Using a for
expression as the body of a method
Because a for
expression yields a result, it can be used as the body of a method that returns a useful value.
This method returns all the values in a given list of integers that are between 3
and 10
:
def between3and10(xs: List[Int]): List[Int] =
for {
x <- xs
if x >= 3
if x <= 10
} yield x
between3and10(List(1, 3, 7, 11)) // : List[Int] = List(3, 7)
def between3and10(xs: List[Int]): List[Int] =
for
x <- xs
if x >= 3
if x <= 10
yield x
between3and10(List(1, 3, 7, 11)) // : List[Int] = List(3, 7)
while
loops
Scala while
loop syntax looks like this:
var i = 0
while (i < 3) {
println(i)
i += 1
}
var i = 0
while i < 3 do
println(i)
i += 1
match
expressions
Pattern matching is a major feature of functional programming languages, and Scala includes a match
expression that has many capabilities.
In the most simple case you can use a match
expression like a Java switch
statement, matching cases based on an integer value.
Notice that this really is an expression, as it evaluates to a result:
// `i` is an integer
val day = i match {
case 0 => "Sunday"
case 1 => "Monday"
case 2 => "Tuesday"
case 3 => "Wednesday"
case 4 => "Thursday"
case 5 => "Friday"
case 6 => "Saturday"
case _ => "invalid day" // the default, catch-all
}
// `i` is an integer
val day = i match
case 0 => "Sunday"
case 1 => "Monday"
case 2 => "Tuesday"
case 3 => "Wednesday"
case 4 => "Thursday"
case 5 => "Friday"
case 6 => "Saturday"
case _ => "invalid day" // the default, catch-all
In this example, the variable i
is tested against the cases shown.
If it’s between 0
and 6
, day
is bound to the string that represents that day of the week.
Otherwise, it matches the catch-all case represented by the character, _
, and day
is bound to the string, "invalid day"
.
Since the cases are considered in the order they are written, and the first matching case is used, the default case, which matches any value, must come last. Any cases after the catch-all will be warned as unreachable cases.
When writing simple
match
expressions like this, it’s recommended to use the@switch
annotation on the variablei
. This annotation provides a compile-time warning if the switch can’t be compiled to atableswitch
orlookupswitch
, which are better for performance.
Using the default value
When you need to access the catch-all, default value in a match
expression, just provide a variable name on the left side of the case
statement instead of _
, and then use that variable name on the right side of the statement as needed:
i match {
case 0 => println("1")
case 1 => println("2")
case what => println(s"You gave me: $what")
}
i match
case 0 => println("1")
case 1 => println("2")
case what => println(s"You gave me: $what")
The name used in the pattern must begin with a lowercase letter. A name beginning with an uppercase letter does not introduce a variable, but matches a value in scope:
val N = 42
i match {
case 0 => println("1")
case 1 => println("2")
case N => println("42")
case n => println(s"You gave me: $n" )
}
val N = 42
i match
case 0 => println("1")
case 1 => println("2")
case N => println("42")
case n => println(s"You gave me: $n" )
If i
is equal to 42
, then case N
will match, and it will print the string "42"
. It won’t reach the default case.
Handling multiple possible matches on one line
As mentioned, match
expressions have many capabilities.
This example shows how to use multiple possible pattern matches in each case
statement:
val evenOrOdd = i match {
case 1 | 3 | 5 | 7 | 9 => println("odd")
case 2 | 4 | 6 | 8 | 10 => println("even")
case _ => println("some other number")
}
val evenOrOdd = i match
case 1 | 3 | 5 | 7 | 9 => println("odd")
case 2 | 4 | 6 | 8 | 10 => println("even")
case _ => println("some other number")
Using if
guards in case
clauses
You can also use guards in the case
s of a match expression.
In this example the second and third case
both use guards to match multiple integer values:
i match {
case 1 => println("one, a lonely number")
case x if x == 2 || x == 3 => println("two’s company, three’s a crowd")
case x if x > 3 => println("4+, that’s a party")
case _ => println("i’m guessing your number is zero or less")
}
i match
case 1 => println("one, a lonely number")
case x if x == 2 || x == 3 => println("two’s company, three’s a crowd")
case x if x > 3 => println("4+, that’s a party")
case _ => println("i’m guessing your number is zero or less")
Here’s another example, which shows how to match a given value against ranges of numbers:
i match {
case a if 0 to 9 contains a => println(s"0-9 range: $a")
case b if 10 to 19 contains b => println(s"10-19 range: $b")
case c if 20 to 29 contains c => println(s"20-29 range: $c")
case _ => println("Hmmm...")
}
i match
case a if 0 to 9 contains a => println(s"0-9 range: $a")
case b if 10 to 19 contains b => println(s"10-19 range: $b")
case c if 20 to 29 contains c => println(s"20-29 range: $c")
case _ => println("Hmmm...")
Case classes and match expressions
You can also extract fields from case
classes—and classes that have properly written apply
/unapply
methods—and use those in your guard conditions.
Here’s an example using a simple Person
case class:
case class Person(name: String)
def speak(p: Person) = p match {
case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo")
case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!")
case _ => println("Watch the Flintstones!")
}
speak(Person("Fred")) // "Fred says, Yubba dubba doo"
speak(Person("Bam Bam")) // "Bam Bam says, Bam bam!"
case class Person(name: String)
def speak(p: Person) = p match
case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo")
case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!")
case _ => println("Watch the Flintstones!")
speak(Person("Fred")) // "Fred says, Yubba dubba doo"
speak(Person("Bam Bam")) // "Bam Bam says, Bam bam!"
Binding matched patterns to variables
You can bind the matched pattern to a variable to use type-specific behavior:
trait Animal {
val name: String
}
case class Cat(name: String) extends Animal {
def meow: String = "Meow"
}
case class Dog(name: String) extends Animal {
def bark: String = "Bark"
}
def speak(animal: Animal) = animal match {
case c @ Cat(name) if name == "Felix" => println(s"$name says, ${c.meow}!")
case d @ Dog(name) if name == "Rex" => println(s"$name says, ${d.bark}!")
case _ => println("I don't know you!")
}
speak(Cat("Felix")) // "Felix says, Meow!"
speak(Dog("Rex")) // "Rex says, Bark!"
trait Animal:
val name: String
case class Cat(name: String) extends Animal:
def meow: String = "Meow"
case class Dog(name: String) extends Animal:
def bark: String = "Bark"
def speak(animal: Animal) = animal match
case c @ Cat(name) if name == "Felix" => println(s"$name says, ${c.meow}!")
case d @ Dog(name) if name == "Rex" => println(s"$name says, ${d.bark}!")
case _ => println("I don't know you!")
speak(Cat("Felix")) // "Felix says, Meow!"
speak(Dog("Rex")) // "Rex says, Bark!"
Using a match
expression as the body of a method
Because match
expressions return a value, they can be used as the body of a method.
This method takes a Matchable
value as an input parameter, and returns a Boolean
, based on the result of the match
expression:
def isTruthy(a: Matchable) = a match {
case 0 | "" | false => false
case _ => true
}
def isTruthy(a: Matchable) = a match
case 0 | "" | false => false
case _ => true
The input parameter a
is defined to be the Matchable
type—which is the root of all Scala types that pattern matching can be performed on.
The method is implemented by matching on the input, providing two cases:
The first one checks whether the given value is either the integer 0
, an empty string or false
and returns false
in this case.
In the default case, we return true
for any other value.
These examples show how this method works:
isTruthy(0) // false
isTruthy(false) // false
isTruthy("") // false
isTruthy(1) // true
isTruthy(" ") // true
isTruthy(2F) // true
Using a match
expression as the body of a method is a very common use.
Match expressions support many different types of patterns
There are many different forms of patterns that can be used to write match
expressions.
Examples include:
- Constant patterns (such as
case 3 =>
) - Sequence patterns (such as
case List(els : _*) =>
) - Tuple patterns (such as
case (x, y) =>
) - Constructor pattern (such as
case Person(first, last) =>
) - Type test patterns (such as
case p: Person =>
)
All of these kinds of patterns are shown in the following pattern
method, which takes an input parameter of type Matchable
and returns a String
:
def pattern(x: Matchable): String = x match {
// constant patterns
case 0 => "zero"
case true => "true"
case "hello" => "you said 'hello'"
case Nil => "an empty List"
// sequence patterns
case List(0, _, _) => "a 3-element list with 0 as the first element"
case List(1, _*) => "list, starts with 1, has any number of elements"
case Vector(1, _*) => "vector, starts w/ 1, has any number of elements"
// tuple patterns
case (a, b) => s"got $a and $b"
case (a, b, c) => s"got $a, $b, and $c"
// constructor patterns
case Person(first, "Alexander") => s"Alexander, first name = $first"
case Dog("Zeus") => "found a dog named Zeus"
// type test patterns
case s: String => s"got a string: $s"
case i: Int => s"got an int: $i"
case f: Float => s"got a float: $f"
case a: Array[Int] => s"array of int: ${a.mkString(",")}"
case as: Array[String] => s"string array: ${as.mkString(",")}"
case d: Dog => s"dog: ${d.name}"
case list: List[?] => s"got a List: $list"
case m: Map[?, ?] => m.toString
// the default wildcard pattern
case _ => "Unknown"
}
def pattern(x: Matchable): String = x match
// constant patterns
case 0 => "zero"
case true => "true"
case "hello" => "you said 'hello'"
case Nil => "an empty List"
// sequence patterns
case List(0, _, _) => "a 3-element list with 0 as the first element"
case List(1, _*) => "list, starts with 1, has any number of elements"
case Vector(1, _*) => "vector, starts w/ 1, has any number of elements"
// tuple patterns
case (a, b) => s"got $a and $b"
case (a, b, c) => s"got $a, $b, and $c"
// constructor patterns
case Person(first, "Alexander") => s"Alexander, first name = $first"
case Dog("Zeus") => "found a dog named Zeus"
// type test patterns
case s: String => s"got a string: $s"
case i: Int => s"got an int: $i"
case f: Float => s"got a float: $f"
case a: Array[Int] => s"array of int: ${a.mkString(",")}"
case as: Array[String] => s"string array: ${as.mkString(",")}"
case d: Dog => s"dog: ${d.name}"
case list: List[?] => s"got a List: $list"
case m: Map[?, ?] => m.toString
// the default wildcard pattern
case _ => "Unknown"
You can also write the code on the right side of the =>
on multiple lines if you think it is easier to read. Here is one example:
count match {
case 1 =>
println("one, a lonely number")
case x if x == 2 || x == 3 =>
println("two's company, three's a crowd")
case x if x > 3 =>
println("4+, that's a party")
case _ =>
println("i'm guessing your number is zero or less")
}
count match
case 1 =>
println("one, a lonely number")
case x if x == 2 || x == 3 =>
println("two's company, three's a crowd")
case x if x > 3 =>
println("4+, that's a party")
case _ =>
println("i'm guessing your number is zero or less")
In Scala 3, match
expressions can be chained:
i match
case odd: Int if odd % 2 == 1 => "odd"
case even: Int if even % 2 == 0 => "even"
case _ => "not an integer"
match
case "even" => true
case _ => false
The match
expression can also follow a period, which simplifies matching on results returned by chained method calls:
List(1, 2, 3)
.map(_ * 2)
.headOption
.match
case Some(value) => println(s"The head is: $value")
case None => println("The list is empty")
try/catch/finally
Like Java, Scala has a try
/catch
/finally
construct to let you catch and manage exceptions.
For consistency, Scala uses the same syntax that match
expressions use and supports pattern matching on the different possible exceptions that can occur.
In the following example, openAndReadAFile
is a method that does what its name implies: it opens a file and reads the text in it, assigning the result to the mutable variable text
:
var text = ""
try {
text = openAndReadAFile(filename)
} catch {
case fnf: FileNotFoundException => fnf.printStackTrace()
case ioe: IOException => ioe.printStackTrace()
} finally {
// close your resources here
println("Came to the 'finally' clause.")
}
var text = ""
try
text = openAndReadAFile(filename)
catch
case fnf: FileNotFoundException => fnf.printStackTrace()
case ioe: IOException => ioe.printStackTrace()
finally
// close your resources here
println("Came to the 'finally' clause.")
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.