Book Image

Learning Concurrent Programming in Scala

By : Aleksandar Prokopec
5 (1)
Book Image

Learning Concurrent Programming in Scala

5 (1)
By: Aleksandar Prokopec

Overview of this book

Table of Contents (18 chapters)
Learning Concurrent Programming in Scala
Credits
Foreword
About the Author
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
Index

Preliminaries


This book assumes basic familiarity with sequential programming. While we advise the readers to get acquainted with the Scala programming language, an understanding of a similar language, such as Java or C#, should be sufficient for reading 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 five, 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 the 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.

A Scala primer

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 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. The Unit type can be omitted, in which case it is inferred automatically by the Scala compiler.

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
}

Where 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 that outputs 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 determine 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

Note

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. 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 by-name parameters to declare a runTwice method, which runs the specified block of code body twice:

def runTwice(body: =>Unit) = {
  body
  body
}

A by-name 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, which 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 zero and four:

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[T] 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, it suffices 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 five. The get method returns an object with the Option[Int] type, which may either be implemented with the Some class, indicating that the number five exists in the map, or the None class, indicating that the number five 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 the 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.