Chapter 3. Expressions and Conditionals

This chapter focuses on Scala’s expressions, statements, and conditionals. The term expression as used in this book indicates a unit of code that returns a value after it has been executed. One or more lines of code can be considered an expression if they are collected together using curly braces ({ and }). This is known as an expression block.

Expressions provide a foundation for functional programming because they make it possible to return data instead of modifying existing data (such as a variable). This enables the use of immutable data, a key functional programming concept where new data is stored in new values instead of in existing variables. Functions, of course, can be used to return new data, but they are in a way just another type of expression.

When all of your code can be organized (or conceptualized) into a collection of one or more hierarchical expressions that return values using immutable data will be straightforward. The return values of expressions will be passed to other expressions or stored into values. As you migrate from using variables, your functions and expressions will have fewer side effects. In other words, they will purely act on the input you give them without affecting any data other than their return value. This is one of the main goals and benefits of functional programming.

As noted earlier, an expression is a single unit of code that returns a value.

Let’s start out with an example of a simple expression in Scala, just a String literal on its own:

scala> "hello"
res0: String = hello

OK, that’s not a very impressive expression. Here’s a more complicated one:

scala> "hel" + 'l' + "o"
res1: String = hello

This example and the previous example are valid expressions that, while implemented differently, generate the same result. What’s important about expressions is the value they return. The entire point of them is to return a value that gets captured and used.

Multiple expressions can be combined using curly braces ({ and }) to create a single expression block. An expression has its own scope, and may contain values and variables local to the expression block. The last expression in the block is the return value for the entire block.

As an example, here is a line with two expressions that would work better as a block:

scala> val x = 5 * 20; val amount = x + 10
x: Int = 100
amount: Int = 110

The only value we really care about keeping is “amount,” so let’s combine the expressions including the “x” value into a block. We’ll use its return value to define the “amount” value:

scala> val amount = { val x = 5 * 20; x + 10 }
amount: Int = 110

The last expression in the block, “x + 10,” determines the block’s return value. The “x” value, previously defined at the same level of “amount,” is now defined locally to the block. The code is now cleaner, because the intent of using “x” to define “amount” is now obvious.

Expression blocks can span as many lines as you need. The preceding example could have been rewritten without the semicolons as follows:

scala> val amount = {
     |   val x = 5 * 20
     |   x + 10
     | }
amount: Int = 110

Expression blocks are also nestable, with each level of expression block having its own scoped values and variables.

Here is a short example demonstrating a three-deep nested expression block:

scala> { val a = 1; { val b = a * 2; { val c = b + 4; c } } }
res5: Int = 6

These examples may not indicate compelling reasons to use expression blocks on their own. However, it is important to understand their syntax and compositional nature because we will revisit them when we cover control structures later in this chapter.

The If..Else conditional expression is a classic programming construct for choosing a branch of code based on whether an expression resolves to true or false. In many languages this takes the form of an “if .. else if .. else” block, which starts with an “if,” continues with zero to many “else if” sections, and ends with a final “else” catch-all statement.

As a matter of practice you can write these same “if .. else if .. else” blocks in Scala and they will work just as you have experienced them in Java and other languages. As a matter of formal syntax, however, Scala only supports a single “if” and optional “else” block, and does not recognize the “else if” block as a single construct.

So how do “else if” blocks still work correctly in Scala? Because “if .. else” blocks are based on expression blocks, and expression blocks can be easily nested, an “if .. else if .. else” expression is equivalent to a nested “if .. else { if .. else }” expression. Logically this is exactly the same as an “if .. else if .. else” block, and as a matter of syntax Scala recognizes the second “if else” as a nested expression of the outer “if .. else” block.

Let’s start exploring actual “if” and “if .. else” blocks by looking at the syntax for the simple “if” block.

Match expressions are akin to C’s and Java’s “switch” statements, where a single input item is evaluated and the first pattern that is “matched” is executed and its value returned. Like C’s and Java’s “switch” statements, Scala’s match expressions support a default or wildcard “catch-all” pattern. Unlike them, only zero or one patterns can match; there is no “fall-through” from one pattern to the next one in line, nor is there a “break” statement that would prevent this fall-through.

The traditional “switch” statement is limited to matching by value, but Scala’s match expressions are an amazingly flexible device that also enables matching such diverse items as types, regular expressions, numeric ranges, and data structure contents. Although many match expressions could be replaced with simple “if .. else if .. else” blocks, doing so would result in a loss of the concise syntax that match expressions offer.

In fact, most Scala developers prefer match expressions over “if .. else” blocks because of their expressiveness and concise syntax.

In this section we will cover the basic syntax and uses of match expressions. As you read through the book, you will pick up new features that may be applicable to match expressions. Try experimenting with them to find new ways to express relationships or equivalence through match expressions.

Syntax: Using a Match Expression

<expression> match {
  case <pattern match> => <expression>
  [case...]
}

Let’s try this out by converting the “if .. else” example from the previous section into a match expression. In this version the Boolean expression is handled first, and then the result is matched to either true or false:

scala> val x = 10; val y = 20
x: Int = 10
y: Int = 20

scala> val max = x > y match {
     |   case true => x
     |   case false => y
     | }
max: Int = 20

The logic works out to the same as in the “if .. else” block but is implemented differently.

Here is another example of a match expression, one that takes an integer status code and tries to return the most appropriate message for it. Depending on the input to the expression, additional actions may be taken besides just returning a value:

scala> val status = 500
status: Int = 500

scala> val message = status match {
     |     case 200 =>
     |         "ok"
     |     case 400 => {
     |         println("ERROR - we called the service incorrectly")
     |         "error"
     |     }
     |     case 500 => {
     |         println("ERROR - the service encountered an error")
     |         "error"
     |     }
     | }
ERROR - the service encountered an error
message: String = error

This match expression prints error messages in case the status is 400 or 500 in addition to returning the message “error.” The println statement is a good example of including more than one expression in a case block. There is no limit to the number of statements and expressions you can have inside a case block, although only the last expression will be used for the match expression’s return value.

You can combine multiple patterns together with a pattern alternative, where the case block will be triggered if any one of the patterns match.

Syntax: A Pattern Alternative

case <pattern 1> | <pattern 2> .. => <one or more expressions>

The pattern alternative makes it possible to prevent duplicated code by reusing the same case block for multiple patterns. Here is an example showing the uses of these pipes (|) to collapse a 7-pattern match expression down to only two patterns:

scala> val day = "MON"
day: String = MON

scala> val kind = day match {
     |   case "MON" | "TUE" | "WED" | "THU" | "FRI" =>
     |     "weekday"
     |   case "SAT" | "SUN" =>
     |     "weekend"
     | }
kind: String = weekday

So far the examples have left open the possibility that a pattern may not be found that matches the input expression. In case this event does occur, for example if the input to the previous example was “MONDAY,” what do you think would happen?

Well, it’s more fun to try it out than to explain, so here is an example of a match expression that fails to provide a matching pattern for the input expression:

scala> "match me" match { case "nope" => "sorry" }
scala.MatchError: match me (of class java.lang.String)
  ... 32 elided

The input of “match me” didn’t match the only given pattern, “nope,” so the Scala compiler treated this as a runtime error. The error type, scala.MatchError, indicates that this is a failure of the match expression to handle its input.

To prevent errors from disrupting your match expression, use a wildcard match-all pattern or else add enough patterns to cover all possible inputs. A wildcard pattern placed as the final pattern in a match expression will match all possible input patterns and prevent a scala.MatchError from occurring.

There are two kinds of wildcard patterns you can use in a match expression: value binding and wildcard (aka “underscore”) operators.

With value binding (aka variable binding) the input to a match expression is bound to a local value, which can then be used in the body of the case block. Because the pattern contains the name of the value to be bound there is no actual pattern to match against, and thus value binding is a wildcard pattern because it will match any input value.

Syntax: A Value Binding Pattern

case <identifier> => <one or more expressions>

Here is an example that tries to match a specific literal and otherwises uses value binding to ensure all other possible values are matched:

scala> val message = "Ok"
message: String = Ok

scala> val status = message match {
     |   case "Ok" => 200
     |   case other => {
     |     println(s"Couldn't parse $other")
     |     -1
     |   }
     | }
status: Int = 200

The value other is defined for the duration of the case block and is assigned the value of message, the input to the match expression.

The other type of wildcard pattern is the use of the wildcard operator. This is an underscore (_) character that acts as an unnamed placeholder for the eventual value of an expression at runtime. As with value binding, the underscore operator doesn’t provide a pattern to match against, and thus it is a wildcard pattern that will match any input value.

Syntax: A Wildcard Operator Pattern

case _ => <one or more expressions>

The wildcard cannot be accessed on the right side of the arrow, unlike with value binding. If you need to access the value of the wildcard in the case block, consider using a value binding, or just accessing the input to the match expression (if available).

Here is a similar example to the one earlier only with a wildcard operator instead of a bound value:

scala> val message = "Unauthorized"
message: String = Unauthorized

scala> val status = message match {
     |   case "Ok" => 200
     |   case _ => {
     |     println(s"Couldn't parse $message")
     |     -1
     |   }
     | }
Couldn't parse Unauthorized
status: Int = -1

In this case the underscore operator matches the runtime value of the input to the match expression. However, it can’t be accessed inside the case block as a bound value would, and thus the input to the match expression is used to create an informative println statement.

Another way to do pattern matching in a match expression is to match the type of the input expression. Pattern variables, if matched, may convert the input value to a value with a different type. This new value and type can then be used inside the case block.

Syntax: Specifying a Pattern Variable

case <identifier>: <type> => <one or more expressions>

The only restriction for pattern variable naming, other than the naming requirements already in place for values and variables, is that they must start with a lowercase letter.

You might be considering the utility of using a match expression to determine a value’s type, given that all values have types and they are typically rather descriptive. The support of polymorphic types in Scala should be a clue to a match expression’s utility. A value of type Int may get assigned to another value of type Any, or it may be returned as Any from a Java or Scala library call. Although the data is indeed an Int, the value will have the higher type Any.

Let’s reproduce this situation by creating an Int, assigning it to an Any, and using a match expression to resolve its true type:

scala> val x: Int = 12180
x: Int = 12180

scala> val y: Any = x
y: Any = 12180

scala> y match {
     |   case x: String => s"'x'"
     |   case x: Double => f"$x%.2f"
     |   case x: Float => f"$x%.2f"
     |   case x: Long => s"${x}l"
     |   case x: Int => s"${x}i"
     | }
res9: String = 12180i

Even though the value given to the match expression has the type Any, the data it is storing was created as an Int. The match expression was able to match based on the actual type of the value, not just on the type that it was given. Thus, the integer 12180, even when given as type Any, could be correctly recognized as an integer and formatted as such.

Loops are the last expression-based control structure we’ll examine in this chapter. A loop is a term for exercising a task repeatedly, and may include iterating through a range of data or repeating until a Boolean expression returns false.

The most important looping structure in Scala is the for-loop, also known as a for-comprehension. For-loops can iterate over a range of data executing an expression every time and optionally return a collection of all the expression’s return values. These for-loops are highly customizable, supporting nested iterating, filtering, and value binding.

To get started we will introduce a new data structure called a Range, which iterates over a series of numbers. Ranges are created using the to or until operator with starting and ending integers, where the to operator creates an inclusive list and the until operator creates an exclusive list.

Syntax: Defining a Numeric Range

<starting integer> [to|until] <ending integer> [by increment]

Next is the basic definition of a for-loop.

Syntax: Iterating with a Basic For-Loop

for (<identifier> <- <iterator>) [yield] [<expression>]

The yield keyword is optional. If it is specified along with an expression, the return value of every expression that gets invoked will be returned as a collection. If it isn’t specified, but the expression is still specified, the expression will be invoked but its return values will not be accessible.

You can define for-loops with parentheses or curly braces. The difference between the two styles comes when using multiple iterators (or other valid for-loop items, as we’ll see), one on each line. With parentheses-based for-loops, each iterator line before the final one must end with a semicolon. With curly-braces-based for-loops, the semicolon after an iterator line is optional.

Let’s start out printing a simple week planner by iterating over the days of a week, from 1 to 7 (inclusive), and printing out a header for each one:

scala> for (x <- 1 to 7) { println(s"Day $x:") }
Day 1:
Day 2:
Day 3:
Day 4:
Day 5:
Day 6:
Day 7:

The curly braces in the loop’s expression (really a statement here because there isn’t a yield keyword) are optional because there is only a single command, but I added them to make this look more like a traditional Java/C “for” loop.

However, what if what I really need is a collection of these “Day X:” messages? Then I can reuse them in other ways, or print them out as many times as I need. The yield keyword is the solution. I can convert the iterated statement into an expression that returns each message instead of printing it out, and add the yield keyword to convert the entire loop into an expression that returns the collection:

scala> for (x <- 1 to 7) yield { s"Day $x:" }
res10: scala.collection.immutable.IndexedSeq[String] = Vector(Day 1:,
Day 2:, Day 3:, Day 4:, Day 5:, Day 6:, Day 7:)

The Scala REPL’s printout is more complicated than we have seen. This one is reporting that res10 has the type IndexedSeq[String], an indexed sequence of String, and is assigned a Vector, one of the implementations of IndexedSeq. Because of Scala’s support for object-oriented polymorphism, a Vector (a subtype of IndexedSeq) can be assigned to an IndexedSeq-typed value.

In a way you can consider this for-loop to be a map, because it takes the expression of rendering the day to a String and applies it for every member of the input range. We have used this to map the range of numbers from 1 to 7 into a collection of messages of the same size. Like other sequences, this collection can now be used as an iterator in other for-loops.

Let’s try it out by creating a for-loop that iterates over the sequence we built and printing each message, this time all on the same line instead of on their own lines. Again we only have a single command in the iterated expression, so this time we will leave off the curly braces because they are not necessary here:

scala> for (day <- res0) print(day + ", ")
Day 1:, Day 2:, Day 3:, Day 4:, Day 5:, Day 6:, Day 7:,

We covered if/else conditions, pattern matching, and loops in detail in this chapter. These structures provide a solid basis for writing core logic in Scala.

However, these three (or two) structures are just as important to learning Scala development as learning about the fundamentals of expressions. The namesake of this chapter—expressions and their return values—are the real core building block of any application. Expressions themselves may seem to be an obvious concept, and devoting an entire chapter to them has the appearance of being overly generous to the topic. The reason I have devoted a chapter to them is that learning to work in terms of expressions is a useful and valuable skill. You should consider expressions when writing code, and even structure your applications around them. Some important principles to keep in mind when writing expressions are (1) how you will organize your code as expressions, (2) how your expressions will derive a return value, and (3) what you will do with that return value.

Expressions, in addition to being a foundation for your code organization, are also a foundation for Scala’s syntax. In this chapter we have defined if/else conditions, pattern matching, and loops in terms of how they are structured around expressions. In the next chapter we will continue this practice by introducing functions as named, reusable expressions and defining them as such. Future chapters will continue this trend of defining concepts and structures in terms of expressions. Thus, understanding the basic nature and syntax of expressions and expression blocks is a crucial key to picking up the syntax for the rest of the language.

While the Scala REPL provides an excellent venue for experimenting with the language’s features, writing more than a line or two of code in it can be challenging. Because you’ll need to start working with more than a few lines of code, it’s time to start working in standalone Scala source files.

The scala command, which launches the Scala REPL, can also be used to evaluate and execute Scala source files:

$ scala <source file>

To test this out, create a new file titled Hello.scala with the following contents:

println("Hello, World")

Then execute it with the scala command:

$ scala Hello.scala
Hello, World

$

You should see the result (“Hello, World”) printed on the next line.

An alternate way to execute external Scala files is with the :load command in the Scala REPL. This is useful if you want to stay in the Scala REPL while still using a text editor or IDE to edit your code.

To test this out, in the same directory you created the Hello.scala file, start the Scala REPL and run :load Hello.scala:

scala> :load Hello.scala
Loading Hello.scala...
Hello, World

scala>

Now that you have the option of developing within the Scala REPL or in a separate text editor or IDE, you can get started with the exercises for this chapter.