This book assumes a basic familiarity with sequential programming. While we advise readers to get acquainted with the Scala programming language, an understanding of a similar language, such as Java or C#, should be sufficient for this book. A basic familiarity with concepts in object-oriented programming, such as classes, objects, and interfaces, is helpful. Similarly, a basic understanding of functional programming principles, such as first-class functions, purity, and type-polymorphism are beneficial in understanding this book but are not a strict prerequisite.
Execution of a Scala program
To better understand the execution model of Scala programs, let's consider a simple program that uses the square
method to compute the square value of the number 5
, and then prints the result to the standard output:
object SquareOf5 extends App {
def square(x: Int): Int = x * x
val s = square(5)
println(s"Result: $s")
}
We can run this program using the Simple Build Tool (SBT), as described in the Preface. When a Scala program runs, the JVM runtime allocates the memory required for the program. Here, we consider two important memory regions--the call stack and the object heap. The call stack is a region of memory in which the program stores information about the local variables and parameters of the currently executed methods. The object heap is a region of memory in which objects are allocated by the program. To understand the difference between the two regions, we consider a simplified scenario of this program's execution.
First, in figure 1, the program allocates an entry to the call stack for the local variable s
. Then, it calls the square
method in figure 2 to compute the value for the local variable s
. The program places the value 5
on the call stack, which serves as the value for the x
parameter. It also reserves a stack entry for the return value of the method. At this point, the program can execute the square
method, so it multiplies the x
parameter by itself, and places the return value 25
on the stack in figure 3. This is shown in the first row in the following illustration:
After the square
method returns the result, the result 25
is copied into the stack entry for the local variable s
, as shown in figure 4. Now, the program must create the string for the println
statement. In Scala, strings are represented as object instances of the String
class, so the program allocates a new String
object to the object heap, as illustrated in figure 5. Finally, in figure 6, the program stores the reference to the newly allocated object into the stack entry x
, and calls the println
method.
Although this demonstration is greatly simplified, it shows the basic execution model for Scala programs. In Chapter 2, Concurrency on the JVM and the Java Memory Model, we will learn that each thread of execution maintains a separate call stack, and that threads mainly communicate by modifying the object heap. We will learn that the disparity between the state of the heap and the local call stack is frequently responsible for certain kinds of error in concurrent programs.
Having seen an example of how Scala programs are typically executed, we now proceed to an overview of Scala features that are essential to understand the contents of this book.
In this section, we present a short overview of the Scala programming language features that are used in the examples in this book. This is a quick and cursory glance through the basics of Scala. Note that this section is not meant to be a complete introduction to Scala. This is to remind you about some of the language's features, and contrast them with similar languages that might be familiar to you. If you would like to learn more about Scala, refer to some of the books referred to in the Summary of this chapter.
A Printer
class, which takes a greeting
parameter and has two methods named printMessage
and printNumber
, is declared as follows:
class Printer(val greeting: String) {
def printMessage(): Unit = println(greeting + "!")
def printNumber(x: Int): Unit = {
println("Number: " + x)
}
}
In the preceding code, the printMessage
method does not take any arguments and contains a single println
statement. The printNumber
method takes a single argument x
of the Int
type. Neither method returns a value, which is denoted by the Unit
type.
We instantiate the class and call its methods as follows:
val printy = new Printer("Hi")
printy.printMessage()
printy.printNumber(5)
Scala allows the declaration of singleton objects. This is like declaring a class and instantiating its single instance at the same time. We saw the SquareOf5
singleton object earlier, which was used to declare a simple Scala program. The following singleton object, named Test
, declares a single Pi
field and initializes it with the value 3.14
:
object Test {
val Pi = 3.14
}
While classes in similar languages extend entities that are called interfaces, Scala classes can extend traits. Scala's traits allow declaring both concrete fields and method implementations. In the following example, we declare the Logging
trait, which outputs a custom error and warning messages using the abstract log
method, and then mix the trait into the PrintLogging
class:
trait Logging {
def log(s: String): Unit
def warn(s: String) = log("WARN: " + s)
def error(s: String) = log("ERROR: " + s)
}
class PrintLogging extends Logging {
def log(s: String) = println(s)
}
Classes can have type parameters. The following generic Pair
class takes two type parameters, P
and Q
, which determines the types of its arguments, named first
and second
:
class Pair[P, Q](val first: P, val second: Q)
Scala has support for first-class function objects, also called lambdas. In the following code snippet, we declare a twice
lambda, which multiplies its argument by two:
val twice: Int => Int = (x: Int) => x * 2
Tip
Downloading the example code:
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
In the preceding code, the (x: Int)
part is the argument to the lambda, and x * 2
is its body. The =>
symbol must be placed between the arguments and the body of the lambda. The same =>
symbol is also used to express the type of the lambda, which is Int => Int
, pronounced as Int
to Int
. In the preceding example, we can omit the type annotation Int => Int
, and the compiler will infer the type of the twice
lambda automatically, as shown in the following code:
val twice = (x: Int) => x * 2
Alternatively, we can omit the type annotation in the lambda declaration and arrive at a more convenient syntax, as follows:
val twice: Int => Int = x => x * 2
Finally, whenever the argument to the lambda appears only once in the body of the lambda, Scala allows a more convenient syntax, as follows:
val twice: Int => Int = _ * 2
First-class functions allow manipulating blocks of code as if they were first-class values. They allow a more lightweight and concise syntax. In the following example, we use byname parameters to declare a runTwice
method, which runs the specified block of code body
twice:
def runTwice(body: =>Unit) = {
body
body
}
A byname parameter is formed by putting the =>
annotation before the type. Whenever the runTwice
method references the body
argument, the expression is re-evaluated, as shown in the following snippet:
runTwice { // this will print Hello twice
println("Hello")
}
Scala for
expressions are a convenient way to traverse and transform collections. The following for
loop prints the numbers in the range from 0 until 10
; where 10
is not included in the range:
for (i <- 0 until 10) println(i)
In the preceding code, the range is created with the expression 0 until 10
; this is equivalent to the expression 0.until(10)
, which calls the method until
on the value 0
. In Scala, the dot notation can sometimes be dropped when invoking methods on objects.
Every for
loop is equivalent to a foreach
call. The preceding for
loop is translated by the Scala compiler to the following expression:
(0 until 10).foreach(i => println(i))
For-comprehensions are used to transform data. The following for-comprehension transforms all the numbers from 0 until 10
by multiplying them by -1
:
val negatives = for (i <- 0 until 10) yield -i
The negatives
value contains negative numbers from 0
until -10
. This for-comprehension is equivalent to the following map
call:
val negatives = (0 until 10).map(i => -1 * i)
It is also possible to transform data from multiple inputs. The following for-comprehension creates all pairs of integers between 0
and 4
:
val pairs = for (x <- 0 until 4; y <- 0 until 4) yield (x, y)
The preceding for-comprehension is equivalent to the following expression:
val pairs = (0 until 4).flatMap(x => (0 until 4).map(y => (x, y)))
We can nest an arbitrary number of generator expressions in a for-comprehension. The Scala compiler will transform them into a sequence of nested flatMap
calls, followed by a map
call at the deepest level.
Commonly used Scala collections include sequences, denoted by the Seq[T]
type; maps, denoted by the Map[K, V]
type; and sets, denoted by the Set[T]
type. In the following code, we create a sequence of strings:
val messages: Seq[String] = Seq("Hello", "World.", "!")
Throughout this book, we rely heavily on the string interpolation feature. Normally, Scala strings are formed with double quotation marks. Interpolated strings are preceded with an s
character, and can contain $
symbols with arbitrary identifiers resolved from the enclosing scope, as shown in the following example:
val magic = 7
val myMagicNumber = s"My magic number is $magic"
Pattern matching is another important Scala feature. For readers with Java, C#, or C background, a good way to describe it is to say that Scala's match
statement is like the switch
statement on steroids. The match
statement can decompose arbitrary datatypes and allows you to express different cases in the program concisely.
In the following example, we declare a Map
collection, named successors
, used to map integers to their immediate successors. We then call the get
method to obtain the successor of the number 5. The get
method returns an object with the Option[Int]
type, which may be implemented either with the Some
class, indicating that the number 5 exists in the map, or the None
class, indicating that the number 5 is not a key in the map. Pattern matching on the Option
object allows proceeding casewise, as shown in the following code snippet:
val successors = Map(1 -> 2, 2 -> 3, 3 -> 4)
successors.get(5) match {
case Some(n) => println(s"Successor is: $n")
case None => println("Could not find successor.")
}
In Scala, most operators can be overloaded. Operator overloading is no different from declaring a method. In the following code snippet, we declare a Position
class with a +
operator:
class Position(val x: Int, val y: Int) {
def +(that: Position) = new Position(x + that.x, y + that.y)
}
Finally, Scala allows defining package objects to store top-level method and value definitions for a given package. In the following code snippet, we declare the package object for the org.learningconcurrency
package. We implement the top level log
method, which outputs a given string and the current thread name:
package org
package object learningconcurrency {
def log(msg: String): Unit =
println(s"${Thread.currentThread.getName}: $msg")
}
We will use the log
method in the examples throughout this book to trace how concurrent programs are executed.
This concludes our quick overview of important Scala features. If you would like to obtain a deeper knowledge about any of these language constructs, we suggest that you check out one of the introductory books on sequential programming in Scala.