1. First Steps in Scala

1.1. Learn to use the Scala interpreter

The easiest way to get started with Scala is by using the Scala interpreter, an interactive “shell” for writing Scala expressions and programs. Simply type an expression into the interpreter and it will evaluate the expression and print the resulting value. The interactive shell for Scala is simply called scala.

You use it by typing scala at a command prompt:

$ scala
Welcome to Scala 2.12.14 (OpenJDK 64-Bit Server VM, Java 17.0.2).
Type in expressions for evaluation. Or try :help.

scala> 2 + 2
res0: Int = 4

scala> res0
res1: Int = 4

scala> res0 + res0
res2: Int = 8

1.2. Define some variables

Scala has two kinds of variables, vals and vars. A val is similar to a final variable in Java. Once initialized, a val can never be reassigned. A var, by contrast, is similar to a non-final variable in Java. A var can be reassigned throughout its lifetime. Here’s a val definition:

scala> val msg = "Hello, world!"
msg: java.lang.String = Hello, world!

scala> msg = "Hello, another world!"
<console>:12: error: reassignment to val
       msg = "Hello, another world!"
           ^

scala> var msg2 = "foo"
msg2: String = foo

scala> msg2 = "bar"
msg2: String = bar

1.3. Write some Scala scripts

Although Scala is designed to help programmers build very large-scale systems, it also scales down nicely to scripting. A script is just a sequence of statements in a file that will be executed sequentially. Put this into a file named hello.scala:

$ cat <<EOF > hello.scala
> println("Hello, world, from a script!")
> EOF

$ scala hello.scala
Hello, world, from a script!

Command line arguments to a Scala script are available via a Scala array named args.

$ cat <<EOF > helloarg.scala
> println("Hello, "+ args(0) +"!")
> EOF

$ scala helloarg.scala planet
Hello, planet!

1.4. Parameterize arrays with types

In Scala, you can instantiate objects, or class instances, using new. When you instantiate an object in Scala, you can parameterize it with values and types. Parameterization means “configuring” an instance when you create it. You parameterize an instance with values by passing objects to a constructor in parentheses. And parameterize an instance with types by specifying one or more types in square brackets.

val greetStrings = new Array[String](3)
  greetStrings(0) = "Hello"
  greetStrings(1) = ", "
  greetStrings(2) = "world!\n"
  for (i <- 0 to 2)
    print(greetStrings(i))

1.5. Use Lists

  • One of the big ideas of the functional style of programming is that methods should not have side effects.

  • Applying this functional philosophy to the world of objects means making objects immutable.

  • For an immutable sequence of objects that share the same type you can use Scala’s List class.

    scala> val oneTwo = List(1, 2)
    oneTwo: List[Int] = List(1, 2)
    
    scala> val threeFour = List(3, 4)
    threeFour: List[Int] = List(3, 4)
    
    scala> val oneTwoThreeFour = oneTwo ::: threeFour
    oneTwoThreeFour: List[Int] = List(1, 2, 3, 4)
    scala> val twoThree = List(2, 3)
    twoThree: List[Int] = List(2, 3)
    
    scala> val oneTwoThree = 1 :: twoThree
    oneTwoThree: List[Int] = List(1, 2, 3)
    
    scala> val oneTwoThree = 1 :: 2 :: 3 :: Nil
    oneTwoThree: List[Int] = List(1, 2, 3)
    scala> oneTwoThree(2)
    res1: Int = 3
    scala> oneTwoThree.head
    res4: Int = 1
    
    scala> oneTwoThree.tail
    res5: List[Int] = List(2, 3)
    
    scala> oneTwoThree.init
    res6: List[Int] = List(1, 2)
    
    scala> oneTwoThree.length
    res7: Int = 3
    
    scala> oneTwoThree.mkString(", ")
    res8: String = 1, 2, 3
    
    scala> oneTwoThree.reverse
    res9: List[Int] = List(3, 2, 1)
  • For a mutable sequence of objects that share the same type you can use Scala’s List class.

    scala> val nums = scala.collection.mutable.ListBuffer(1, 2)
    nums: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2)
    
    scala> nums += 3
    nums: nums.type = ListBuffer(1, 2, 3)
    
    scala> nums
    res15: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3)
    
    scala> nums ++= Seq(4, 5)
    res16: nums.type = ListBuffer(1, 2, 3, 4, 5)

1.6. Use sets and maps

scala> var jetSet = Set("Boeing", "Airbus")
jetSet: scala.collection.immutable.Set[String] = Set(Boeing, Airbus)

scala> jetSet += "Lear"

scala> println(jetSet)
Set(Boeing, Airbus, Lear)
scala> import scala.collection.mutable.Set
import scala.collection.mutable.Set

scala> val movieSet = Set("Hitch", "Poltergeist")
movieSet: scala.collection.mutable.Set[String] = Set(Poltergeist, Hitch)

scala> movieSet += "Shrek"
res4: movieSet.type = Set(Poltergeist, Shrek, Hitch)

scala> println(movieSet)
Set(Poltergeist, Shrek, Hitch)
scala> val romanNumeral = Map(
     |     1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V"
     |   )
romanNumeral: scala.collection.immutable.Map[Int,String] = Map(5 -> V, 1 -> I, 2 -> II, 3 -> III, 4 -> IV)

scala> println(romanNumeral)
Map(5 -> V, 1 -> I, 2 -> II, 3 -> III, 4 -> IV)
scala> import scala.collection.mutable.Map
import scala.collection.mutable.Map

scala> val treasureMap = Map[Int, String]()
treasureMap: scala.collection.mutable.Map[Int,String] = Map()

scala> treasureMap += (1 -> "Go to island.")
res8: treasureMap.type = Map(1 -> Go to island.)

scala> treasureMap += (2 -> "Find big X on ground.")
res9: treasureMap.type = Map(2 -> Find big X on ground., 1 -> Go to island.)

scala> treasureMap += (3 -> "Dig.")
res10: treasureMap.type = Map(2 -> Find big X on ground., 1 -> Go to island., 3 -> Dig.)

scala> println(treasureMap(2))
Find big X on ground.

1.7. Learn to recognize the functional style

Scala allows you to program in an imperative style, but encourages you to adopt a more functional style.

A balanced attitude for Scala programmers:

  • Prefer vals, immutable objects, and methods without side effects. Reach for them first.

  • Use vars, mutable objects, and methods with side effects when you have a specific need and justification for them.

    // imperative style
    def printArgs(args: Array[String]): Unit = {
      var i = 0
      while (i < args.length) {
        println(args(i))
        i += 1
      }
    }
    
    // not purely functional style with side effects—in this case,
    // its side effect is printing to the standard output stream.
    def printArgs(args: Array[String]): Unit = {
      for (arg <- args)
        println(arg)
    }
    
    // or this:
    def printArgs(args: Array[String]): Unit = {
      args.foreach(println)
    }
    
    // purely functional style without side effects or vars in sight.
    def formatArgs(args: Array[String]) = args.mkString("\n")
    
    val res = formatArgs(Array("zero", "one", "two"))
    assert(res == "zero\none\ntwo")
    
    println(formatArgs(args))

2. Classes and Objects

A class is a blueprint for objects. Once you define a class, you can create objects from the class blueprint with the keyword new.

Inside a class definition, you place fields and methods, which are collectively called members.

  • Fields, which you define with either val or var, are vari- ables that refer to objects.

  • Methods, which you define with def, contain executable code.

  • The fields hold the state, or data, of an object, whereas the methods use that data to do the computational work of the object.

    scala> class ChecksumAccumulator {
         |   private var sum = 0
         |   def add(b: Byte) { sum += b }
         |   def checksum(): Int = ~(sum & 0xFF) + 1
         | }
    defined class ChecksumAccumulator
    
    scala> val acc = new ChecksumAccumulator
    acc: ChecksumAccumulator = ChecksumAccumulator@4756971e
    
    scala> acc.add(22)
    
    scala> acc.checksum
    res13: Int = -22

2.1. Singleton object

A singleton object definition looks like a class definition, except instead of the keyword class you use the keyword object.

import scala.collection.mutable.Map

object ChecksumAccumulator {
  private val cache = Map[String, Int]()

  def calculate(s: String): Int =
    if (cache.contains(s))
      cache(s)
    else {
      val acc = new ChecksumAccumulator
      for (c <- s)
        acc.add(c.toByte)
        val cs = acc.checksum()
        cache += (s -> cs)
        cs
    }
}

2.2. Companion object

When a singleton object shares the same name with a class, it is called that class’s companion object.

  • You must define both the class and its companion object in the same source file.

  • The class is called the companion class of the singleton object.

  • A class and its companion object can access each other’s private members.

    // In file ChecksumAccumulator.scala
    class ChecksumAccumulator {
      private var sum = 0
      def add(b: Byte) { sum += b }
      def checksum(): Int = ~(sum & 0xFF) + 1
    }
    
    import scala.collection.mutable.Map
    
    object ChecksumAccumulator {
      private val cache = Map[String, Int]()
    
      def calculate(s: String): Int =
        if (cache.contains(s))
          cache(s)
        else {
          val acc = new ChecksumAccumulator
          for (c <- s)
            acc.add(c.toByte)
            val cs = acc.checksum()
            cache += (s -> cs)
            cs
        }
    }

2.3. Standalone object

A singleton object that does not share the same name with a companion class is called a standalone object.

  • You can use standalone objects for many purposes, including collecting related utility methods together, or defining an entry point to a Scala application.

    // In file Summer.scala
    import ChecksumAccumulator.calculate
    
    object Summer {
      def main(args: Array[String]) {
        for (arg <- args)
          println(arg +": "+ calculate(arg))
      }
    }
    $ scalac Summer.scala ChecksumAccumulator.scala
    
    $ scala Summer Hello World
    Hello: -244
    World: -8

3. Basic Types and Operations

3.1. Some basic types

Collectively, types Byte, Short, Int, Long, and Char are called integral types. The integral types plus Float and Double are called numeric types.

Other than String, which resides in package java.lang, all of the types are members of package scala.

Value Type Range

Byte

8-bit signed two’s complement integer (-27 to 27 - 1, inclusive)

Short

16-bit signed two’s complement integer (-215 to 215 - 1, inclusive)

Int

32-bit signed two’s complement integer (-231 to 231 - 1, inclusive)

Long

64-bit signed two’s complement integer (-263 to 263 - 1, inclusive)

Char

16-bit unsigned Unicode character (0 to 216 - 1, inclusive)

String

a sequence of Chars

Float

32-bit IEEE 754 single-precision float

Double

64-bit IEEE 754 double-precision float

Boolean

true or false

3.2. Operators are methods

Scala provides a rich set of operators for its basic types.

These operators are actually just a nice syntax for ordinary method calls.

  • For example, 1 + 2 really means the same thing as (1).+(2).

    In other words, class Int contains a method named + that takes an Int and returns an Int result.

    This + method is invoked when you add two Ints:

    scala> val sum = 2 + 2
    sum: Int = 4
    
    scala> val sumMore = (2).+(2)
    sumMore: Int = 4

    In fact, Int contains several overloaded + methods that take different parameter types.

    scala> val longSum = 2 + 2L
    longSum: Long = 4
    
    scala> (2).+(2L)
    res2: Long = 4
    
    scala> val longSumMore = (2).+(2L)
    longSumMore: Long = 4

The + symbol is an operator—an infix operator to be specific.

  • Operator notation is not limited to methods like + that look like operators in other languages.

  • You can use any method in operator notation.

    scala> val s = "Hello, world!"
    s: String = Hello, world!
    
    scala> s indexOf 'o' // Scala invokes s.indexOf('o')
    res4: Int = 4
Any method can be an operator

In Scala operators are not special language syntax: any method can be an operator.

What makes a method an operator is how you use it.

  • When you write s.indexOf('o'), indexOf is not an operator.

  • But when you write s indexOf 'o', indexOf is an operator, because you’re using it in operator notation.

The infix operation notation, which means the method to invoke sits betwwen the object and the parameter or parameters you wish to pass to the method, as "2 + 2". Scala also has two other operation notations: prefix and postfix.

In contrast to the infix operator notation—in which operators take two operands, one to the left and the other to the right—prefix and postfix operators are unary: they take just one operand.

  • In prefix notation, the operand is to the right of the operator.

    Some examples of prefix operators are -2.0, !found, and ~0xFF.

    As with the infix operators, these prefix operators are a shorthand way of invoking methods.

    In this case, however, the name of the method has "unary_" prepended to the operator character.

    For instance, Scala will transform the expression -2.0 into the method invocation (2.0).unary_-.

    scala> -2.0
    res5: Double = -2.0
    
    scala> (2.0).unary_-
    res6: Double = -2.0

    The only identifiers that can be used as prefix operators are +, -, !, and ~.

  • Postfix operators are methods that take no arguments, when they are invoked without a dot or parentheses.

    In Scala, you can leave off empty parentheses on method calls.

    The convention is that you include parentheses if the method has side effects, such as println(),

    but you can leave them off if the method has *no side effects*, such as `toLowerCase` invoked on a String:
    scala> val s = "Hello, world!"
    s: String = Hello, world!
    
    scala> s.toLowerCase
    res10: String = hello, world!
    
    scala> s toLowerCase
    <console>:13: warning: postfix operator toLowerCase should be enabled
    by making the implicit value scala.language.postfixOps visible.
    This can be achieved by adding the import clause 'import scala.language.postfixOps'
    or by setting the compiler option -language:postfixOps.
    See the Scaladoc for value scala.language.postfixOps for a discussion
    why the feature should be explicitly enabled.
           s toLowerCase
             ^
    res11: String = hello, world!

A simple MyInt type like Int:

class MyInt(v: Int) {
  private val value = v

  def + (x: MyInt) = MyInt(value + x.value)

  def + (x: Int) = MyInt(value + x)

  def unary_-() = MyInt(-value)

  override def equals(x: Any) = x match {
    case MyInt(v) => value == v
    case _ => false
  }

  override def hashCode() = value.hashCode

  override def toString() = s"$v"
}

object MyInt {
  def apply(v: Int) = new MyInt(v)
  def unapply(v: Int): Option[Int] = Some(v)
}

implicit def covertInttoMyInt(x: Int) = MyInt(x)

val x1 = MyInt(2)
val nums = Seq(
  MyInt(2),
  x1 + x1,
  x1 + 2,
  2 + x1,
  - x1,
  )

println(nums.mkString("(", ", ", ")"))
// (2, 4, 4, 4, -2)

4. Functional Objects

// a functional objects that do not have any mutable state.
class Rational(n: Int, d: Int) { // class parameters and constructors
  require(d != 0) // checking preconditions

  private val g = gcd(n.abs, d.abs) // private fields and methods

  // adding fields
  val numer = n / g
  val denom = d / g

  def this(n: Int) = this(n, 1) // auxiliary constructor

  def + (that: Rational): Rational = // defining operators
    new Rational(
      this.numer * that.denom + that.numer * denom, // self references
      denom * that.denom
    )

  def + (i: Int): Rational = // method overloading
    new Rational(numer + i * denom, denom)

  def * (that: Rational): Rational =
    new Rational(numer * that.numer, denom * that.denom)

  // reimplementing the toString method
  override def toString = numer +"/"+ denom

  // private fields and methods
  private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
}

// implicit conversions
object ImplicitConversions {
  import scala.language.implicitConversions

  implicit def intToRational(x: Int) = new Rational(x)
}

object Main {
  def main(args: Array[String]) {
    val x = new Rational(2, 3)
    val y = new Rational(2)
    println(s"${x} + ${y} = ${x + y}")
    println(s"${x} + 2 = ${x + 2}")

    import ImplicitConversions._
    println(s"2 + ${x} = ${2 + x}")
  }
}

5. Built-in Control Structures

Scala has only a handful of built-in control structures. The only control structures are if, while, for, try, match, and function calls.

One thing you will notice is that almost all of Scala’s control structures result in some value.

5.1. If expressions

// imperative style
var filename = "default.txt"
if (!args.isEmpty)
  filename = args(0)
// Scala’s idiom for conditional initialization.
val filename =
  if (!args.isEmpty) args(0)
  else "default.txt"

5.2. While loops

// while loop
def gcdLoop(x: Long, y: Long): Long = {
  var a = x
  var b = y
  while (a != 0) {
    val temp = a a=b%a
    b = temp
  }
  b
}

// do-while
var line = ""
do {
  line = readLine()
  println("Read: "+ line)
} while (line != "")

// Scala assignment always results in the unit value, ().
var line = ""
while ((line = readLine()) != "") // This doesn’t work!
  println("Read: "+ line)

5.3. For expressions

  • Iteration through collections

    scala> val filesHere = (new java.io.File(".")).listFiles
    filesHere: Array[java.io.File] = Array(./powerlog)
    
    scala> for (file <- filesHere)
         | println(file)
    ./powerlog
    
    scala> for (i <- 1 to 4)
         | println("Iteration "+ i)
    Iteration 1
    Iteration 2
    Iteration 3
    Iteration 4
    
    scala> for (i <- 1 until 4)
         | println("Iteration "+ i)
    Iteration 1
    Iteration 2
    Iteration 3
    
    // Not common in Scala...
    scala> for (i <- 0 to filesHere.length - 1)
         | println(filesHere(i))
    ./powerlog
  • Filtering

    val filesHere = (new java.io.File(".")).listFiles
    for (file <- filesHere if file.getName.endsWith(".scala"))
      println(file)
    
    // imperative style
    for (file <- filesHere)
      if (file.getName.endsWith(".scala"))
        println(file)
    
    // keep adding `if` clauses to include more filters
    for (
      file <- filesHere
      if file.isFile
      if file.getName.endsWith(".scala")
    ) println(file)
  • Nested iteration

    // If you add multiple <- clauses, you will get nested “loops.
    def fileLines(file: java.io.File) =
      scala.io.Source.fromFile(file).getLines().toList
    
    def grep(pattern: String) =
      for (
         file <- filesHere
         if file.getName.endsWith(".scala");
         line <- fileLines(file)
         if line.trim.matches(pattern)
      ) println(file +": "+ line.trim)
    
    grep(".*gcd.*")
  • Mid-stream variable bindings

    def fileLines(file: java.io.File) =
      scala.io.Source.fromFile(file).getLines().toList
    
    def grep(pattern: String) =
      for (
         file <- filesHere
         if file.getName.endsWith(".scala");
         line <- fileLines(file)
         // You can do this by binding the result to a new variable using an equals sign (=).
         // The bound variable is introduced and used just like a val, only with the val keyword left out.
         trimmed = line.trim
         if trimmed.matches(pattern)
      ) println(file +": "+ trimmed)
    
    grep(".*gcd.*")
  • Producing a new collection

    // for [clauses] yield [body]
    def scalaFiles =
      for {
        file <- filesHere
        if file.getName.endsWith(".scala")
      } yield file
    for (file <- filesHere if file.getName.endsWith(".scala")) {
      yield file  // Syntax error!
    }

5.4. Exception handling with try expressions

  • Throwing exceptions

    Throwing an exception looks the same as in Java. You create an exception object and then you throw it with the throw keyword:

    throw new IllegalArgumentException

    Although it may seem somewhat paradoxical, in Scala, throw is an expression that has a result type. Here is an example in which that result type matters:

    // What happens here is that if n is even, half will be initialized to half of n.
    // If n is not even, an exception will be thrown before half can be initialized to anything at all.
    // Technically, an exception throw has type Nothing.
    val half =
      if (n % 2 == 0) {
        n/2
      }else{
        throw new RuntimeException("n must be even")
      }
  • Catching exceptions

    The syntax for catch clauses was chosen for its consistency with an important part of Scala: pattern matching.

    import java.io.FileReader
    import java.io.FileNotFoundException
    import java.io.IOException
    
    try {
      val f = new FileReader("input.txt")
      // Use and close file
    } catch {
      case ex: FileNotFoundException => // Handle missing file
      case ex: IOException => // Handle other I/O error
      case _: Exception => // Handle other error
    }
  • The finally clause

    import java.io.FileReader
    
    val file = new FileReader("input.txt")
    try {
      // Use the file
    } finally {
      file.close()  // Be sure to close the file
    }
Loan Pattern
// In file Loan.scala
object Disposable {
  // using statement with C# style (disposable pattern)
  def using(closer: AutoCloseable)(op: => Unit) {
    try {
      op
    } finally {
      closer.close()
    }
  }
}

object Main {
  def main(args: Array[String]) {
    import Disposable._
    import java.io.{BufferedReader, FileReader, PrintWriter}
    import java.util.Date

    val writer = new PrintWriter("date.txt")
    using(writer) {
      writer.println(new Date)
    }

    val reader = new BufferedReader(new FileReader("date.txt"))
    using(reader) {
      println(reader.readLine())
    }
  }
}
  • Yielding a value

    As with most other Scala control structures, try-catch-finally results in a value.

    • The result is that of the try clause if no exception is thrown, or the relevant catch clause if an exception is thrown and caught.

    • If an exception is thrown but not caught, the expression has no result at all.

    • The value computed in the finally clause, if there is one, is dropped.

    • Usually finally clauses do some kind of clean up such as closing a file; they should not normally change the value computed in the main body or a catch clause of the try.

      import java.net.URL
      
      import java.net.MalformedURLException
      def urlFor(path: String) =
        try {
          new URL(path)
        } catch {
          case e: MalformedURLException =>
            new URL("http://www.scala-lang.org")
        }
    The best way to think of finally clauses is as a way to ensure some side effect happens, such as closing an open file.
    scala> def f(): Int = try { return 1 } finally { return 2 }
    f: ()Int
    
    scala> f
    res9: Int = 2
    
    scala> def g(): Int = try { 1 } finally { 2 }
    <console>:11: warning: a pure expression does nothing in statement position
           def g(): Int = try { 1 } finally { 2 }
                                              ^
    g: ()Int
    
    scala> g
    res10: Int = 1

5.5. Match expressions

Scala’s match expression lets you select from a number of alternatives, just like switch statements in other languages.

// A match expression that yields a value.
val firstArg = if (!args.isEmpty) args(0) else ""
val friend =
  firstArg match {
    case "salt" => "pepper"
    case "chips" => "salsa"
    case "eggs" => "bacon"
    // The default case is specified with an underscore (_), a wildcard symbol
    // frequently used in Scala as a placeholder for a completely unknown value.
    case _ => "huh?"
  }
println(friend)

5.6. Living without break and continue

You may have noticed that there has been no mention of break or continue. Scala leaves out these commands because they do not mesh well with function literals. It is clear what continue means inside a while loop, but what would it mean inside a function literal? While Scala supports both imperative and functional styles of programming, in this case it leans slightly towards functional programming in exchange for simplifying the language. Do not worry, though. There are many ways to program without break and continue, and if you take advantage of function literals, those alternatives can often be shorter than the original code.

// searching through an argument list for a string that ends with “.scala”
// but does not start with a hyphen.
//
// int i = 0;                // This is Java
// boolean foundIt = false;
// while (i < args.length) {
//   if (args[i].startsWith("-")) {
//     i = i + 1;
//     continue;
//   }
//
//   if (args[i].endsWith(".scala")) {
//     foundIt = true;
//     break;
//   }
//
//   i = i + 1;
// }
//
// Looping without break or continue in Scala
var i = 0
var foundIt = false
while (i < args.length && !foundIt) {
  if (!args(i).startsWith("-") && args(i).endsWith(".scala")) {
    foundIt = true
  }

  i = i + 1
}
println(foundIt)

If you wanted to get rid of the vars in the above code snippet, one approach you could try is to rewrite the loop as a recursive function.

// Rewrite the loop as a recursive function to get rid of the vars
def searchFrom(i: Int): Int = {
  if (i >= args.length) -1
  else if (args(i).startsWith("-")) searchFrom(i + 1)
  else if (args(i).endsWith(".scala")) i
  else searchFrom(i + 1)
}
val foundIt = searchFrom(0) >= 0
println(foundIt)

If after all this discussion you still feel the need to use break, there’s help in Scala’s standard library. Class Breaks in package scala.util.control offers a break method, which can be used to exit the an enclosing block that’s marked with breakable.

import scala.util.control.Breaks._

import java.io._

val in = new BufferedReader(new InputStreamReader(System.in))
breakable {
  while (true) {
    println("? ")
    if (in.readLine() == "") break
  }
}

The Breaks class implements break by throwing an exception that is caught by an enclosing application of the breakable method. Therefore, the call to break does not need to be in the same method as the call to breakable.

5.7. Refactoring imperative-style code

//   1   2   3   4   5   6   7   8   9
//   2   4   6   8  10  12  14  16  18
//   3   6   9  12  15  18  21  24  27
//   4   8  12  16  20  24  28  32  36
//   5  10  15  20  25  30  35  40  45
//   6  12  18  24  30  36  42  48  54
//   7  14  21  28  35  42  49  56  63
//   8  16  24  32  40  48  56  64  72
//   9  18  27  36  45  54  63  72  81
object MultiTable {
  // imperative-style code
  // def printMultiTable() {
  //   for( row <- 1 to 9) {
  //     for( col <- 1 to 9) {
  //       val prod = (row * col).toString
  //       val padding = " " * (4 - prod.size)
  //       print(s"${padding}${prod}")
  //     }
  //     println()
  //   }
  // }

  // Returns a row as sequence
  def makeRowSeq(row: Int): Seq[Int] = { // ???
    for (col <- 1 to 9) yield row * col
  }

  def makeRow(row: Int): String = { // ???
    makeRowSeq(row).
    map(_.toString()).
    map(prod => s"${" " * (4 - prod.size)}${prod}").
    mkString("")
  }

  def multiTable(): String = { // ???
    val tableSeq =
      for (row <- 1 to 9) yield {
        makeRow(row)
      }
    tableSeq.mkString("\n")
  }

  def printMultiTable() {
    val table = multiTable
    println(table)
  }

  def main(args: Array[String]) {
    printMultiTable()
  }
}

6. Functions and Closures

When programs get larger, you need some way to divide them into smaller, more manageable pieces. For dividing up control flow, Scala offers an approach familiar to all experienced programmers: divide the code into functions. In fact, Scala offers several ways to define functions that are not present in Java. Besides methods, which are functions that are members of some object, there are also functions nested within functions, function literals, and function values.

6.1. Methods

The most common way to define a function is as a member of some object. Such a function is called a method.

import scala.io.Source

// LongLines with a private processLine method.
object LongLines {

  def processFile(filename: String, width: Int) {
    val source = Source.fromFile(filename)
    for (line <- source.getLines())
      processLine(filename, width, line)
  }

  private def processLine(filename: String,
    width: Int, line: String) {
      if (line.length > width)
        println(filename +": "+ line.trim)
  }
}

object FindLongLines {
  def main(args: Array[String]) {
    val width = args(0).toInt
    for (arg <- args.drop(1))
      LongLines.processFile(arg, width)
  }
}

6.2. Local functions

import scala.io.Source

// You can define functions inside other functions.
// Just like local variables, such local functions
// are visible only in their enclosing block.
object LongLines {

  def processFile(filename: String, width: Int) {

    // Local functions can access the parameters of their enclosing function.
    def processLine(line: String) {
      if (line.length > width)
        println(filename +": "+ line)
    }

    val source = Source.fromFile(filename)
    for (line <- source.getLines())
      processLine(line)
  }
}

6.3. First-class functions

Scala has first-class functions.

  • Not only can you define functions and call them,

  • but you can write down functions as unnamed literals and then pass them around as *values.

A function literal is compiled into a class that when instantiated at runtime is a function value.

  • Every function value is an instance of some class that extends one of several FunctionN traits in package scala,

  • such as Function0 for functions with no parameters, Function1 for functions with one parameter, and so on.

  • Each FunctionN trait has an apply method used to invoke the function.

    // The => designates that this function converts the thing on the left (any integer x)
    // to the thing on the right (x + 1).
    // So, this is a function mapping any integer x to x + 1.
    scala> (x: Int) => x + 1
    res1: Int => Int = $Lambda$1469/0x00000008011c9838@75fdf03c
    
    // Function values are objects, so you can store them in variables if you like.
    scala> var increase = (x: Int) => x + 1
    increase: Int => Int = $Lambda$1470/0x00000008011ca638@2d74a59b
    
    // They are functions, too, so you can invoke them using the usual parentheses function-call notation.
    scala> increase(10)
    res2: Int = 11
    
    // Each FunctionN trait has an `apply` method used to invoke the function.
    scala> increase.apply(10)
    res3: Int = 11
    
    scala> val someNumbers = List(-11, -10, -5, 0, 5, 10)
    someNumbers: List[Int] = List(-11, -10, -5, 0, 5, 10)
    
    // Takes a function as an argument and invokes that function on each of its elements.
    scala> someNumbers.map((x: Int) => 2 * x)
    res0: List[Int] = List(-22, -20, -10, 0, 10, 20)

6.4. Short forms of function literals

Scala provides a number of ways to leave out redundant information and write function literals more briefly.

  • One way to make a function literal more brief is to leave off the parameter types.

    scala> someNumbers.map((x) => 2 * x)
    res1: List[Int] = List(-22, -20, -10, 0, 10, 20)
  • A second way to remove useless characters is to leave out parentheses around a parameter whose type is inferred.

    scala> someNumbers.map(x => 2 * x)
    res2: List[Int] = List(-22, -20, -10, 0, 10, 20)

6.5. Placeholder syntax

To make a function literal even more concise, you can use underscores as placeholders for one or more parameters, so long as each parameter appears only one time within the function literal.

someNumbers.map(2 * _)
res3: List[Int] = List(-22, -20, -10, 0, 10, 20)

// Multiple underscores mean multiple parameters, not reuse of a single parameter repeatedly.
// The first underscore represents the first parameter,
// the second underscore the second parameter,
// the third underscore the third parameter, and so on.
scala> val f = (_: Int) + (_: Int)
f: (Int, Int) => Int = $Lambda$1558/0x00000008011d2a88@129b4b70

scala> f(5, 10)
res13: Int = 15

scala> someNumbers.reduce(f)
res14: Int = -11

scala> someNumbers.reduce(_ + _)
res11: Int = -11

6.6. Partially applied functions

Although the previous examples substitute underscores in place of individual parameters, you can also replace an entire parameter list with an underscore. For example, rather than writing println(_), you could write println _. Here’s an example:

// Remember that you need to leave a space between the function name and the underscore,
// because otherwise the compiler will think you are referring to a different symbol,
// such as for example, a method named `println_`, which likely does not exist.
someNumbers.foreach(println _)

Scala treats this short form exactly as if you had written the following:

someNumbers.foreach(x => println(x))

In Scala, when you invoke a function, passing in any needed arguments, you apply that function to the arguments.

scala> def sum(a: Int, b: Int, c: Int) = a + b + c
sum: (a: Int, b: Int, c: Int)Int

// You could apply the function sum to the arguments 1, 2, and 3 like this:
scala> sum(1, 2, 3)
res0: Int = 6

A partially applied function is an expression in which you don’t supply all of the arguments needed by the function. Instead, you supply some, or none, of the needed arguments.

// create a partially applied function expression involving sum, in which you supply none of the three required
// arguments.
//  The resulting function can then be stored in a variable.
scala> val a = sum _
a: (Int, Int, Int) => Int = $Lambda$1560/0x00000008011cfc58@3ba37b4a

// Given this code, the Scala compiler instantiates a function value
// that takes the three integer parameters missing from
// the partially applied function expression, `sum _`, and assigns a reference to
// that new function value to the variable `a`.
// When you apply three arguments to this new function value, it will turn around
// and invoke `sum`, passing in those same three arguments:
scala> a(1, 2, 3)
res1: Int = 6

// This function value is an instance of a class generated automatically by
// the Scala compiler from `sum _`, the partially applied function expression.
// The class generated by the compiler has an apply method that takes three arguments.
// The generated class extends `trait Function3`, which declares a three-arg apply method.
// The generated class’s `apply` method takes three arguments because three is the number
// of arguments missing in the `sum _` expression.
// The Scala compiler translates the expression `a(1, 2, 3)` into an invocation of the
// function value’s `apply` method, passing in the three arguments 1, 2, and 3.
// Thus, `a(1, 2, 3)` is a short form for:
scala> a.apply(1, 2, 3)
res2: Int = 6

// Another way to think about this kind of expression, in which an underscore is used to represent
// an entire parameter list, is as a way to *transform a `def` into a `function value`*.
//  Although you can’t assign a method or nested function to a variable, or pass it as an argument
// to another function, you can do these things if you wrap the method or nested function in a
// function value by placing an underscore after its name.

// In the case of `sum _`, you are applying it to none of its arguments.
// But you can also express a partially applied function by supplying some but not all of
// the required arguments.
scala> val b = sum(1, _, 3)
b: Int => Int = $Lambda$1566/0x00000008011d7690@61f38079

scala> b(5)
res3: Int = 9

If you are writing a partially applied function expression in which you leave off all parameters, such as println _ or sum _, you can express it more concisely by leaving off the underscore if a function is required at that point in the code.

someNumbers.foreach(println _)

// You could just write:
someNumbers.foreach(println)

This last form is allowed only in places where a function is required, such as the invocation of foreach in this example. The compiler knows a function is required in this case, because foreach requires that a function be passed as an argument. In situations where a function is not required, attempting to use this form will cause a compilation error.

scala> val c = sum
<console>:12: error: missing argument list for method sum
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `sum _` or `sum(_,_,_)` instead of `sum`.
       val c = sum
               ^

scala> val d = sum _
d: (Int, Int, Int) => Int = $Lambda$1567/0x00000008011d8c58@19ca9708

scala> d(10, 20, 30)
res4: Int = 60

6.7. Closures

You can, however, refer to variables in function body defined elsewhere:

(x: Int) => x + more  // how much more?

This function adds “more” to its argument, but what is more? From the point of view of this function, more is a free variable, because the function literal does not itself give a meaning to it. The x variable, by contrast, is a bound variable, because it does have a meaning in the context of the function: it is defined as the function’s lone parameter, an Int. If you try using this function literal by itself, without any more defined in its scope, the compiler will complain:

scala> (x: Int) => x + more
<console>:12: error: not found: value more
       (x: Int) => x + more
                       ^

On the other hand, the same function literal will work fine so long as there is something available named more:

scala> var more = 1
more: Int = 1

scala> val addMore = (x: Int) => x + more
addMore: Int => Int = $Lambda$1568/0x00000008011dd218@2a7b81e3

scala> addMore(10)
res0: Int = 11

The function value (the object) that’s created at runtime from this function literal is called a closure.

  • The name arises from the act of “closing” the function literal by “capturing” the bindings of its free variables.

    A function literal with no free variables, such as (x: Int) ⇒ x + 1, is called a closed term, where a term is a bit of source code.

    Thus a function value created at runtime from this function literal is not a closure in the strictest sense, because (x: Int) ⇒ x + 1 is already closed as written.

    But any function literal with free variables, such as (x: Int) ⇒ x + more, is an open term.

    Therefore, any function value created at runtime from (x: Int) ⇒ x + more will by definition require that a binding for its free variable, more, be captured.

    The resulting function value, which will contain a reference to the captured more variable, is called a closure,

    therefore, because the function value is the end product of the act of closing the open term, (x: Int) ⇒ x + more.

Intuitively, Scala’s closures capture variables themselves, not the value to which variables refer.

scala> more = 9999
more: Int = 9999

scala> addMore(10)
res3: Int = 10009
import scala.collection.mutable.ListBuffer

val funcList = ListBuffer[() => Unit]()
var x = 0
for (i <- 1 to 3) {
  x = i // x: reassignment
  funcList += (() => println(x))
}
funcList.foreach(_())

// Output:
// 3
// 3
// 3

Each time this function is called it will create a new closure. Each closure will access the more variable that was active when the closure was created.

scala> def makeIncreaser(more: Int) = (x: Int) => x + more
makeIncreaser: (more: Int)Int => Int

scala> val inc1 = makeIncreaser(1)
inc1: Int => Int = $Lambda$1579/0x00000008011d20c8@7b8f6b2c

scala> val inc9999 = makeIncreaser(9999)
inc9999: Int => Int = $Lambda$1579/0x00000008011d20c8@bdb64b3

scala> inc1(10)
res4: Int = 11

scala> inc9999(10)
res5: Int = 10009

6.8. Special function call forms

  • Repeated parameters

    • Scala allows you to indicate that the last parameter to a function may be repeated.

    • This allows clients to pass variable length argument lists to the function.

    • To denote a repeated parameter, place an asterisk after the type of the parameter.

      scala> def echo(args: String*) =
           | for (arg <- args) println(arg)
      echo: (args: String*)Unit
      
      scala> echo()
      
      scala> echo("one")
      one
      
      scala> echo("hello", "world")
      hello
      world
      
      // Nevertheless, if you have an array of the appropriate type, and you attempt
      // to pass it as a repeated parameter, you’ll need to append
      // the array argument with a colon and an _* symbol, like this:
      scala> val arr = Array("What's", "up", "doc?")
      arr: Array[String] = Array(What's, up, doc?)
      
      scala> echo(arr: _*)
      What's
      up
      doc?
  • Named arguments

    In a normal function call, the arguments in the call are matched one by one in the order of the parameters of the called function:

    scala> def speed(distance: Float, time: Float): Float =
         | distance / time
    speed: (distance: Float, time: Float)Float
    
    scala> speed(100, 10)
    res5: Float = 10.0
    
    scala> speed(distance = 100, time = 10)
    res6: Float = 10.0
    
    scala> speed(time = 10, distance = 100)
    res7: Float = 10.0
  • Default parameter values

    scala> def printTime(out: java.io.PrintStream = Console.out) =
         | out.println("time = "+ System.currentTimeMillis())
    printTime: (out: java.io.PrintStream)Unit
    
    scala> printTime()
    time = 1651414239220

6.9. Tail recursion

def approximate(guess: Double): Double =
  if (isGoodEnough(guess)) guess
  else approximate(improve(guess))

Functions like approximate, which call themselves as their last action, are called tail recursive.

If you want the approximate function to run faster, you might be tempted to write it with a while loop to try and speed it up, like this:

def approximateLoop(initialGuess: Double): Double = {
  var guess = initialGuess
  while (!isGoodEnough(guess))
    guess = improve(guess)
    guess
}

However, in the case of approximate above, the Scala compiler is able to apply an important optimization.

The Scala compiler detects tail recursion and replaces it with a jump back to the beginning of the function, after updating the function parameters with the new values.

  • Tracing tail-recursive functions

    A tail-recursive function will not build a new stack frame for each call; all calls will execute in a single frame.

    This function is not tail recursive, because it performs an increment operation after the recursive call.

    // file in Boom.scala
    object Boom {
      def boom(x: Int): Int = {
        if (x == 0) {
          throw new Exception("boom!")
        } else {
          // This function is not tail recursive,
          // because it performs an increment operation after the recursive call.
          boom(x - 1) + 1
        }
      }
    
      def main(args: Array[String]) {
        boom(3)
      }
    }
    
    // Output:
    // $ scala Boom.scala
    // java.lang.Exception: boom!
    // 	at Main$.boom(Boom.scala:4)
    // 	at Main$.boom(Boom.scala:8)
    // 	at Main$.boom(Boom.scala:8)
    // 	at Main$.boom(Boom.scala:8)
    // 	at Main$.main(Boom.scala:13)
    // 	at Main.main(Boom.scala)

    If you now modify boom so that it does become tail recursive:

    // file in Bang.scala
    object Bang {
      def bang(x: Int): Int = {
        if (x == 0) {
          throw new Exception("bang!")
        } else {
          bang(x - 1)
        }
      }
    
      def main(args: Array[String]) {
        bang(5)
      }
    }
    
    // Output:
    // $ scala Bang.scala
    // java.lang.Exception: bang!
    // 	at Main$.bang(Bang.scala:5)
    // 	at Main$.main(Bang.scala:12)
    // 	at Main.main(Bang.scala)

    If you think you might be confused by tail-call optimizations when looking at a stack trace, you can turn them off by giving the following argument to the scala shell or to the scalac compiler:

    -g:notailcalls

    With that option specified, you will get a longer stack trace:

    $ scala -g:notailcalls Bang.scala
    java.lang.Exception: bang!
    	at Main$.bang(Bang.scala:5)
    	at Main$.bang(Bang.scala:7)
    	at Main$.bang(Bang.scala:7)
    	at Main$.bang(Bang.scala:7)
    	at Main$.bang(Bang.scala:7)
    	at Main$.bang(Bang.scala:7)
    	at Main$.main(Bang.scala:12)
    	at Main.main(Bang.scala)
  • Limits of tail recursion

    The use of tail recursion in Scala is fairly limited, because the JVM instruction set makes implementing more advanced forms of tail recursion very difficult. Scala only optimizes directly recursive calls back to the same func- tion making the call.

    If the recursion is indirect, as in the following example of two mutually recursive functions, no optimization is possible:

    def isEven(x: Int): Boolean =
      if (x == 0) true else isOdd(x - 1)
    
    def isOdd(x: Int): Boolean =
      if (x == 0) false else isEven(x - 1)

7. Control Abstraction: Higher-order function and Currying

7.1. Reducing code duplication

These higher-order functions—functions that take functions as parameters—give you extra opportunities to condense and simplify code.

// object FileMatcher {
//
//   private def filesHere = (new java.io.File(".")).listFiles
//
//   def filesEnding(query: String) =
//     for (file <- filesHere; if file.getName.endsWith(query))
//       yield file
//
//   def filesContaining(query: String) =
//     for (file <- filesHere; if file.getName.contains(query))
//       yield file
//
//   def filesRegex(query: String) =
//     for (file <- filesHere; if file.getName.matches(query))
//       yield file
// }
//
// Experienced programmers will notice all of this repetition and wonder
// if it can be factored into a common helper function. Doing it the obvious
// way does not work, however. You would like to be able to do the following:
//
// def filesMatching(query: String, matcher: (String, String) => Boolan) =
//   for (file <- filesHere; if matcher(file.getName, query))
//     yield file
//
// Given this new filesMatching helper method, you can simplify the three
// searching methods by having them call the helper method, passing in an
// appropriate function:
//
// def filesEnding(query: String) =
//   filesMatching(query, _.endsWith(_))
//
// def filesContaining(query: String) =
//   filesMatching(query, _.contains(_))
//
// def filesRegex(query: String) =
//   filesMatching(query, _.matches(_))
//
// The function literals used in the above, such as `_.endsWith(_)`
// and `_.contains(_)`, are instantiated at runtime into function values
// that are not closures, because they don’t capture any free variables.
//
// By contrast, the function literal `_.endsWith(query)`, used in the most
// recent example, contains one bound variable, the argument represented
// by the underscore, and one free variable named query.
//
// Using closures to reduce code duplication.
object FileMatcher {

  private def filesHere = (new java.io.File(".")).listFiles

  private def filesMatching(matcher: String => Boolean) = {
    for (file <- filesHere if matcher(file.getName))
      yield file
  }

  def filesEnding(query: String) =
    // eq. filesMatching((fileName: String) => fileName.endsWith(query))
    filesMatching(_.endsWith(query))

  def filesContaining(query: String) =
    // eq. filesMatching((fileName: String) => fileName.contains(query))
    filesMatching(_.contains(query))

  def filesRegex(query: String) =
    // eq. filesMatching((fileName: String) => fileName.matches(query))
    filesMatching(_.matches(query))
}

7.2. Currying

A curried function is applied to multiple argument lists, instead of just one.

// Defining and invoking a “plain old” function.
scala> def plainOldSum(x: Int, y: Int) = x + y
plainOldSum: (x: Int, y: Int)Int

scala> plainOldSum(2, 2)
res0: Int = 4

// Defining and invoking a curried function.
scala> def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Int

scala> curriedSum(2)(2)
res1: Int = 4

What’s happening here is that when you invoke curriedSum, you actually get two traditional function invocations back to back. The first function invocation takes a single Int parameter named x, and returns a function value for the second function. This second function takes the Int parameter y.

You can use the placeholder notation to use curriedSum in a partially applied function expression, like this:

scala> val twoPlus = curriedSum(2) _
twoPlus: Int => Int = $Lambda$1624/0x00000008011d0838@1fcd9ce1

scala> twoPlus(2)
res4: Int = 4

7.3. Writing new control structures

Consider now a more widely used coding pattern: open a resource, operate on it, and then close the resource.

// open a resource, operate on it, and then close the resource.
def withPrintWriter(file: File, op: PrintWriter => Unit) {
  val writer = new PrintWriter(file)
  try {
    op(writer)
  } finally {
    writer.close()
  }
}

// Given such a method, you can use it like this:
withPrintWriter(
  new File("date.txt"),
  writer => writer.println(new java.util.Date)
)

In any method invocation in Scala in which you’re passing in exactly one argument, you can opt to use curly braces to surround the argument instead of parentheses.

scala> println("Hello, world!")
Hello, world!

scala> println { "Hello, world!" }
Hello, world!

scala> val g = "Hello, world!"
g: String = Hello, world!

scala> g.substring(7, 9)
res7: String = wo

scala> g.substring { 7, 9 }
<console>:1: error: ';' expected but ',' found.
       g.substring { 7, 9 }
                      ^

The purpose of this ability to substitute curly braces for parentheses for passing in one argument is to enable client programmers to write function literals between curly braces. This can make a method call feel more like a control abstraction.

The new version differs from the old one only in that there are now two parameter lists with one parameter each instead of one parameter list with two parameters.

// open a resource, operate on it, and then close the resource.
def withPrintWriter(file: File)(op: PrintWriter => Unit) {
  val writer = new PrintWriter(file)
  try {
    op(writer)
  } finally {
    writer.close()
  }
}

// Given such a method, you can use it with a more pleasing syntax:
val file = new File("date.txt")
withPrintWriter(file) {
  writer => writer.println(new java.util.Date)
}

7.4. By-name parameters

What if you want to implement something more like if or while, however, where there is no value to pass into the code between the curly braces? To help with such situations, Scala provides by-name parameters.

The myAssert function will take a function value as input and consult a flag to decide what to do. If the flag is set, myAssert will invoke the passed function and verify that it returns true. If the flag is turned off, myAssert will quietly do nothing at all.

// Without using by-name parameters, you could write myAssert like this:
var assertionsEnabled = true

def myAssert(predicate: () => Boolean) =
  if (assertionsEnabled && !predicate())
    throw new AssertionError

// The definition is fine, but using it is a little bit awkward:
myAssert(() => 5 > 3)

// You would really prefer to leave out the empty parameter list and `=>` symbol
// in the function literal and write the code like this:
myAssert(5 > 3) // Won’t work, because missing `() =>`

By-name parameters exist precisely so that you can do this. To make a by-name parameter, you give the parameter a type starting with `=>` instead of `() =>`.

// Using a by-name parameter.
def byNameAssert(predicate: => Boolean) =
  if (assertionsEnabled && !predicate)
    throw new AssertionError

// The result is that using byNameAssert looks exactly like
// using a built-in control structure:
byNameAssert(5 > 3)

A by-name type, in which the empty parameter list, (), is left out, is only allowed for parameters. There is no such thing as a by-name variable or a by-name field.

Now, you may be wondering why you couldn’t simply write myAssert using a plain old Boolean for the type of its parameter, like this:

def boolAssert(predicate: Boolean) =
  if (assertionsEnabled && !predicate)
    throw new AssertionError

This formulation is also legal, of course, and the code using this version of boolAssert would still look exactly as before:

boolAssert(5 > 3)

Nevertheless, one difference exists between these two approaches that is important to note.

  • Because the type of boolAssert’s parameter is Boolean, the expression inside the parentheses in boolAssert(5 > 3) is evaluated before the call to boolAssert.

    The expression 5 > 3 yields true, which is passed to boolAssert.

  • By contrast, because the type of byNameAssert’s predicate parameter is => Boolean, the expression inside the parentheses in byNameAssert(5 > 3) is not evaluated before the call to byNameAssert.

    Instead a function value will be created whose apply method will evaluate 5 > 3, and this function value will be passed to byNameAssert.

The difference between the two approaches, therefore, is that if assertions are disabled, you’ll see any side effects that the expression inside the parentheses may have in boolAssert, but not in byNameAssert.

scala> var assertionsEnabled = false
assertionsEnabled: Boolean = false

scala> byNameAssert(1 / 0 == 0)

scala> boolAssert(1 / 0 == 0)
java.lang.ArithmeticException: / by zero
  ... 28 elided

8. Composition and Inheritance

Scala’s support for object-oriented programming:

  • abstract classes,

  • parameterless methods,

  • extending classes,

  • overriding methods and fields,

  • parametric fields,

  • invoking superclass constructors,

  • polymorphism and dynamic binding,

  • final members and classes,

  • and factory objects and methods.

8.1. Abstract classes

// The abstract modifier signifies that the class may have abstract members
// that do not have an implementation.
// As a result, you cannot instantiate an abstract class.
abstract class Element {
  // A method is abstract if it does not have an implementation (i.e., no equals sign or body).
  def contents: Array[String]
}

// error: class Element is abstract; cannot be instantiated
new Element

8.2. Defining parameterless methods

// Defining parameterless methods width and height.
// Note that none of Element’s three methods has a
// parameter list, not even an empty one.
abstract class Element {
  def contents: Array[String]
  def height: Int = contents.length
  def width: Int = if (height == 0) 0 else contents(0).length
}

Such parameterless methods are quite common in Scala. By contrast, methods defined with empty parentheses, such as def height(): Int, are called empty-paren methods.

  • The recommended convention is to use a parameterless method whenever there are no parameters and the method accesses mutable state only by reading fields of the containing object (in particular, it does not change mutable state).

    This convention supports the uniform access principle, which says that client code should not be affected by a decision to implement an attribute as a field or method.

// Implement width and height as fields instead of methods,
// simply by changing the def in each definition to a val.
abstract class Element {
  def contents: Array[String]
  val height = contents.length
  val width =
    if (height == 0) 0 else contents(0).length
}

Scala is very liberal when it comes to mixing parameterless and empty-paren methods.

  • In particular, you can override a parameterless method with an empty-paren method, and vice versa.

  • You can also leave off the empty parentheses on an invocation of any function that takes no arguments.

    Array(1, 2, 3).toString
    "abc".length

In principle it’s possible to leave out all empty parentheses in Scala function calls. However, it is recommended to still write the empty parentheses when the invoked method represents more than a property of its receiver ob- ject.

"hello".length  // no () because no side-effect
println()       // better to not drop the ()

To summarize,

  • it is encouraged style in Scala to define methods that take no parameters and have no side effects as parameterless methods, i.e., leaving off the empty parentheses.

  • On the other hand, you should never define a method that has side-effects without parentheses, because then invocations of that method would look like a field selection. So your clients might be surprised to see the side effects.

Similarly,

  • whenever you invoke a function that has side effects, be sure to include the empty parentheses when you write the invocation.

  • Another way to think about this is if the function you’re calling performs an operation, use the parentheses, but if it merely provides access to a property, leave the parentheses off.

8.3. Extending classes

// Defining ArrayElement as a subclass of Element.
//
// 1. type ArrayElement is a `subtype` of the type Element.
// 2. class ArrayElement is called a `subclass` of class Element,
//    that `inherits` all non-private memebers from class Element.
// 3. Conversely, Element is a `superclass` of ArrayElement.
class ArrayElement(conts: Array[String]) extends Element {
  // The contents method `overrides` (or, alternatively: `implements`)
  // abstract method contents in class Element.
  //
  // NOTE: because the returned array is mutalbe,
  // consider returning a `defensive copy` of the array instead.
  //
  // This's a `composition` relationship between ArrayElement and Array[String]
  def contents: Array[String] = conts
}

8.4. Overriding methods and fields

The uniform access principle is just one aspect where Scala treats fields and methods more uniformly than Java. Another difference is that in Scala, fields and methods belong to the same namespace. This makes it possible for a field to override a parameterless method.

// Overriding a parameterless method with a field.
class ArrayElement(conts: Array[String]) extends Element {
  val contents: Array[String] = conts
}

// $ javap -p ArrayElement.class
// public class ArrayElement extends Element {
//   private final java.lang.String[] contents;
//   public java.lang.String[] contents();
//   public ArrayElement(java.lang.String[]);
// }

On the other hand, in Scala it is forbidden to define a field and method with the same name in the same class, whereas it is allowed in Java.

// This is Java
class CompilesFine {
  private int f = 0;
  public int f() {
    return 1;
  }
}

// But the corresponding Scala class would not compile:
class WontCompile {
  private var f = 0 // Won’t compile, because a field
  def f = 1         // and method have the same name
}

Java’s four namespaces are fields, methods, types, and packages. By contrast, Scala has just two namespaces are:

  • values (fields, methods, packages, and singleton objects)

  • types (class and trait names)

The reason Scala places fields and methods into the same namespace is precisely so you can override a parameterless method with a val, something you can’t do with Java.

The reason that packages share the same namespace as fields and methods in Scala is to enable you to import packages in addition to just importing the names of types, and the fields and methods of singleton objects.

8.5. Defining parametric fields

// Defining contents as a parametric field.
//
// Note that now the contents parameter is prefixed by val.
// This is a shorthand that defines at the same time a parameter
// and field with the same name.
// Specifically, class ArrayElement now has an (unreassignable) field
// contents, which can be accessed from outside the class.
// The field is initialized with the value of the parameter.
class ArrayElement(
  val contents: Array[String]
) extends Element

// $ javap -p ArrayElement.class
// public class ArrayElement extends Element {
//   private final java.lang.String[] contents;
//   public java.lang.String[] contents();
//   public ArrayElement(java.lang.String[]);
// }
// You can also prefix a class parameter with var,
//   in which case the corresponding field would be reassignable.
// Finally, it is possible to add modifiers
//   such as private, protected, or override to these parametric fields,
//   just as you can do for any other class member.
class Cat {
  val dangerous = false
}

class Tiger(
  override val dangerous: Boolean,
  private var age: Int
) extends Cat

// $ javap -p Cat.class Tiger.class
// public class Cat {
//   private final boolean dangerous;
//   public boolean dangerous();
//   public Cat();
// }
//
// public class Tiger extends Cat {
//   private final boolean dangerous;
//   private int age;
//   public boolean dangerous();
//   private int age();
//   private void age_$eq(int);
//   public Tiger(boolean, int);
// }

8.6. Invoking superclass constructors

// Invoking a superclass constructor.
//
// To invoke a superclass constructor, you simply place
//   the argument or arguments you want to pass in parentheses
//   following the name of the superclass.
class LineElement(s: String) extends ArrayElement(Array(s)) {
  override def width = s.length
  override def height = 1
}

8.7. Using override modifiers

Scala requires override modifier for all members that override a concrete member in a parent class.

  • The modifier is optional if a member implements an abstract member with the same name.

  • The modifier is forbidden if a member does not override or implement some other member in a base class.

abstract class Alice {
  def foo(): Unit
  def bar() {}
}

class Suber extends Bob {
  def foo() {}
  def bar() {} // error: method bar needs `override' modifier
}

8.8. Polymorphism and dynamic binding

You can create more forms of Element by defining new Element subclasses,

  • this phenomenon is polymorphism,

  • the method invocations on variables and expressions are dynamically bound.

abstract class Element {
  override def toString() = "Element"
}

class ArrayElement extends Element {
  override def toString() = "ArrayElement"
}

class LineElement extends ArrayElement {
  override def toString() = "LineElement"
}

val e1: Element = new ArrayElement
val e2: Element = new LineElement
println(e1)
println(e2)
// Output:
// ArrayElement
// LineElement

8.9. Declaring final members

In Scala, as in Java, you do this by adding a final modifier to the member.

abstract class Element {
  final override def toString() = "Element"
}

final class ArrayElement extends Element {
  // error: method toString cannot override final member
  override def toString() = "ArrayElement"
}

// error: illegal inheritance from final class ArrayElement
class LineElement extends ArrayElement {
}

9. Defining a factory object

A factory object contains methods that construct other objects.

  • Clients would then use these factory methods for object construction rather than constructing the objects directly with new.

  • An advantage of this approach is that object creation can be centralized and the details of how objects are represented with classes can be hidden.

  • This hiding will both make your library simpler for clients to understand, because less detail is exposed, and provide you with more opportunities to change your library’s implementation later without breaking client code.

10. Scala’s Hierarchy

In Scala, every class inherits from a common superclass named Any.

  • Because every class is a subclass of Any, the methods defined in Any are “universal” methods: they may be invoked on any object.

    // Class Any at the top of the hierarchy, defines methods that include the following:
    //
    // Because every class inherits from Any, every object in a Scala program can be compared
    //   using `==`, `!=`, or `equals`; hashed using `##` or `hashCode`;
    //   and formatted using `toString`.
    // The equality and inequality methods, `==` and `!=`, are declared `final` in class Any, so
    //   they cannot be overridden in subclasses.
    // The `==` method is essentially the same as `equals` and
    //     `!=` is always the negation of `equals`.
    // So individual classes can tailor what `==` or `!=` means by overriding the `equals` method.
    final def ==(that: Any): Boolean
    
    final def !=(that: Any): Boolean
    
    def equals(that: Any): Boolean
    
    def ##: Int
    
    def hashCode: Int
    
    def toString: String

Scala also defines some interesting classes at the bottom of the hierarchy, Null and Nothing, which essentially act as common subclasses.

  • For example, just as Any is a superclass of every other class, Nothing is a subclass of every other class.

    • Class Null is the type of the null reference; it is a subclass of every reference class (i.e., every class that itself inherits from AnyRef).

    • Null is not compatible with value types.

    • Type Nothing is at the very bottom of Scala’s class hierarchy; it is a subtype of every other type.

      // The return type of error is Nothing, which tells users
      // that the method will not return normally (it throws an exception instead).
      def error(message: String): Nothing =
        throw new RuntimeException(message)

The root class Any has two subclasses: AnyVal and AnyRef.

  • AnyVal is the parent class of every built-in value class in Scala.

The other value class, Unit, corresponds roughly to Java’s void type; it is used as the result type of a method that does not otherwise return an interesting result. Unit has a single instance value, which is written ().

scala> Nil
res22: scala.collection.immutable.Nil.type = List()

scala> null
res23: Null = null

scala> None
res24: None.type = None

scala> val x = (() => {})()
x: Unit = ()

11. Traits

Traits are a fundamental unit of code reuse in Scala. A trait encapsulates method and field definitions, which can then be reused by mixing them into classes. Unlike class inheritance, in which each class must inherit from just one superclass, a class can mix in any number of traits.

11.1. How traits work

A trait definition looks just like a class definition except that it uses the keyword trait.

// The definition of trait Philosophical.
trait Philosophical { //  extends AnyRef
  def philosophize() {
    println("I consume memory, therefore I am!")
  }
}

Once a trait is defined, it can be mixed in to a class using either the extends or with keywords.

Scala programmers “mix in” traits rather than inherit from them, because mixing in a trait has important differences from the multiple inheritance found in many other languages.

// Mixing in a trait using extends.
//
// Class From subclasses AnyRef (the superclass of Philosophical)
//   and mixes in Philosophical.
//
// Methods inherited from a trait can be used just like
//   methods inherited from a superclass.
class Frog extends Philosophical {
  override def toString = "green"
}

val frog = new Frog
frog.philosophize()
// Output:
// I consume memory, therefore I am!

val phil: Philosophical = frog
frog.philosophize()
// Output:
// I consume memory, therefore I am!

If you wish to mix a trait into a class that explicitly extends a superclass, you use extends to indicate the superclass and with to mix in the trait.

class Animal

trait HasLegs

// Mixing in multiple traits using with.
class Frog extends Animal with Philosophical with HasLegs {
  override def toString = "green"

  // override philosophize
  override def philosophize() {
    println("It ain't easy being "+ toString +"!")
  }
}

val phrog: Philosophical = new Frog
phrog.philosophize()
// Output:
// It ain't easy being green!

trait Philosophical {
  def philosophize() {}
}
trait Philosophical {
  def philosophize() {
    println("I'm thinking, therefore I am!")
  }
}

class Zhangsan {
  override def toString() = "法外狂徒!"
}

val philZhang: Philosophical = new Zhangsan with Philosophical
philZhang.philosophize()
println(philZhang)
// Output:
// I'm thinking, therefore I am!
// 法外狂徒!

Traits can declare fields and maintain state.

  • Trait cannot have any “class” parameters, i.e., parameters passed to the primary constructor of a class.

    trait NoPoint(x: Int, y: Int) // Does not compile
  • The other difference between classes and traits is that whereas in classes, super calls are statically bound, in traits, they are dynamically bound.

    If you write “super.toString” in a class, you know exactly which method implementation will be invoked.

    When you write the same thing in a trait, however, the method implementation to invoke for the super call is undefined when you define the trait.

    Rather, the implementation to invoke will be determined anew each time the trait is mixed into a concrete class. This curious behavior of super is key to allowing traits to work as stackable modifications.

11.2. Thin versus rich interfaces

One major use of traits is to automatically add methods to a class in terms of methods the class already has. That is, traits can enrich a thin interface, making it into a rich interface.

11.3. Traits as stackable modifications

You have now seen one major use of traits: turning a thin interface into a rich one. Now we’ll turn to a second major use: providing stackable modifications to classes. Traits let you modify the methods of a class, and they do so in a way that allows you to stack those modifications with each other.

Given a class that implements such a queue, you could define traits to perform modifications such as these:

  • Doubling: double all integers that are put in the queue

  • Incrementing: increment all integers that are put in the queue

  • Filtering: filter out negative integers from a queue

These three traits represent modifications, because they modify the behavior of an underlying queue class rather than defining a full queue class themselves. The three are also stackable. You can select any of the three you like, mix them into a class, and obtain a new class that has all of the modifications you chose.

// Abstract class IntQueue.
abstract class IntQueue {
  def get(): Int

  def put(x: Int)
}

import scala.collection.mutable.ArrayBuffer

// A BasicIntQueue implemented with an ArrayBuffer.
class BasicIntQueue extends IntQueue {
  private val buf = new ArrayBuffer[Int]

  def get() = buf.remove(0)

  def put(x: Int) { buf += x }
}

scala> val queue = new BasicIntQueue
queue: BasicIntQueue = BasicIntQueue@9468ea6

scala> queue.put(10)

scala> queue.put(20)

scala> queue.get()
res3: Int = 10

scala> queue.get()
res4: Int = 20
// The Doubling stackable modification trait.
//
// The Doubling trait has two funny things going on.
//
//   1. The first is that it declares a superclass, IntQueue.
//      This declaration means that the trait can only be mixed into
//      a class that also extends IntQueue.
//      Thus, you can mix Doubling into BasicIntQueue, but not into other types.
//   2. The second funny thing is that the trait has a super call on a method
//      declared abstract. Such calls are illegal for normal classes, because
//      they will certainly fail at runtime.
//      For a trait, however, such a call can actually succeed. Since super calls
//      in a trait are dynamically bound, the super call in trait Doubling will
//      work so long as the trait is mixed in after another trait or class that
//      gives a concrete definition to the method.
trait Doubling extends IntQueue {
  // This arrangement is frequently needed with traits that implement stackable modifications.
  // To tell the compiler you are doing this on purpose, you must mark
  //   such methods as `abstract override`.
  // This combination of modifiers is only allowed for members of traits, not classes,
  //  and it means that the trait must be mixed into some class that has a concrete definition of
  //    the method in question.
  abstract override def put(x: Int) { super.put(2 * x) }
}

// Note that MyQueue defines no new code.
// It simply identifies a class and mixes in a trait.
scala> class MyQueue extends BasicIntQueue with Doubling
defined class MyQueue

scala> val queue = new MyQueue
queue: MyQueue = MyQueue@130a6c5c

scala> queue.put(10)

scala> queue.get()
res7: Int = 20

// Mixing in a trait when instantiating with new.
scala> val queue = new BasicIntQueue with Doubling
queue: BasicIntQueue with Doubling = $anon$1@d050328

scala> queue.put(10)

scala> queue.get()
res9: Int = 20

To see how to stack modifications, we need to define the other two modification traits, Incrementing and Filtering.

// Stackable modification traits Incrementing and Filtering.
trait Incrementing extends IntQueue {
  abstract override def put(x: Int) { super.put(x + 1) }
}

trait Filtering extends IntQueue {
  abstract override def put(x: Int) {
    if (x >= 0) super.put(x)
  }
}

scala> val queue = new BasicIntQueue with Incrementing with Filtering
queue: BasicIntQueue with Incrementing with Filtering = $anon$1@458a5362

scala> queue.put(-1); queue.put(0); queue.put(1)

scala> queue.get()
res1: Int = 1

scala> queue.get()
res2: Int = 2

// The order of mixins is significant.
//  The precise rules, roughly speaking, traits further to the right take effect first.
//    When you call a method on a class with mixins, the method
//      in the trait furthest to the right is called first.
//    If that method calls super, it invokes the method in the next trait to its left,
//      and so on.
scala> val queue = new BasicIntQueue with Filtering with Incrementing
queue: BasicIntQueue with Filtering with Incrementing = $anon$1@1c8d5d80

scala> queue.put(-1); queue.put(0); queue.put(1)

scala> queue.get()
res4: Int = 0

scala> queue.get()
res5: Int = 1

scala> queue.get()
res6: Int = 2

11.4. Why not multiple inheritance?

Traits are a way to inherit from multiple class-like constructs, but they differ in important ways from the multiple inheritance present in many languages.

One difference is especially important: the interpretation of super.

  • With multiple inheritance, the method called by a super call can be determined right where the call appears.

  • With traits, the method called is determined by a linearization of the classes and traits that are mixed into a class.

11.5. To trait, or not to trait?

Whenever you implement a reusable collection of behavior, you will have to decide whether you want to use a trait or an abstract class.

  • If the behavior will not be reused, then make it a concrete class.

    It is not reusable behavior after all.

  • If it might be reused in multiple, unrelated classes, make it a trait.

    Only traits can be mixed into different parts of the class hierarchy.

  • If you want to inherit from it in Java code, use an abstract class.

    Since traits with code do not have a close Java analog, it tends to be awkward to inherit from a trait in a Java class.+ Inheriting from a Scala class, meanwhile, is exactly like inheriting from a Java class. * As one exception, a Scala trait with only abstract members translates directly to a Java interface, so you should feel free to define such traits even if you expect Java code to inherit from it.

  • If you plan to distribute it in compiled form, and you expect outside groups to write classes inheriting from it, you might lean towards using an abstract class.

    The issue is that when a trait gains or loses a member, any classes that inherit from it must be recompiled, even if they have not changed.

    If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine.

  • If efficiency is very important, lean towards using a class.

    Most Java runtimes make a virtual method invocation of a class member a faster operation than an interface method invocation.

    Traits get compiled to interfaces and therefore may pay a slight performance overhead.

    However, you should make this choice only if you know that the trait in question constitutes a performance bottleneck and have evidence that using a class instead actually solves the problem.

  • If you still do not know, after considering the above, then start by making it as a trait.

    You can always change it later, and in general using a trait keeps more options open.

12. Packages and Imports

12.1. Putting code in packages

Scala code resides in the Java platform’s global hierarchy of packages. The example code you’ve seen so far in this book has been in the unnamed package. You can place code into named packages in Scala in two ways.

First, you can place the contents of an entire file into a package by putting a package clause at the top of the file:

// Placing the contents of an entire file into a package.
package bobsrockets.navigation
class Navigator

The other way you can place code into packages in Scala is more like C# namespaces. You follow a package clause by a section in curly braces that contains the definitions that go into the package. This syntax is called a packaging.

// Long form of a simple package declaration.
package bobsrockets.navigation {
  class Navigator
}

When code is divided into a package hierarchy, it doesn’t just help people browse through the code. It also tells the compiler that code in the same package is related in some way to each other. Scala takes advantage of this relatedness by allowing short, unqualified names when accessing code that is in the same package.

Scala provides a package named root that is outside any package a user can write. Put another way, every top-level package you can write is treated as a member of package root.

package launch {
  class Booster3
}

package spaceX {
  package navigation {
    package launch {
      class Booster1

      class MissionControl {
        val booster1 = new Booster1
        val booster2 = new spaceX.launch.Booster2
        val booster3 = new _root_.launch.Booster3
      }
    }
  }

  package launch {
    class Booster2
  }
}

13.1. Imports

In Scala, packages and their members can be imported using import clauses.

// Bob’s delightful fruits, ready for import.
package bobsdelights

abstract class Fruit(
  val name: String,
  val color: String
)

object Fruits {
  object Apple extends Fruit("apple", "red")
  object Orange extends Fruit("orange", "orange")
  object Pear extends Fruit("pear", "yellowish")
  val menu = List(Apple, Orange, Pear)
}
  • An import clause makes members of a package or object available by their names alone

    without needing to prefix them by the package or object name.

    // The first of these corresponds to Java’s single type import,
    //
    // the second to Java’s on-demand import.
    //
    // The only difference is that Scala’s on-demand imports are written
    // with a trailing underscore (`_`) instead of an asterisk (`*`) (after all,
    // `*` is a valid identifier in Scala!).
    //
    // The third import clause above corresponds to Java’s import of static class fields.
    //
    // easy access to Fruit
    import bobsdelights.Fruit
    
    // easy access to all members of bobsdelights
    import bobsdelights._
    
    // easy access to all members of Fruits
    import bobsdelights.Fruits._
  • Imports in Scala can appear anywhere, not just at the beginning of a compilation unit.

    Also, they can refer to arbitrary values.

    // Importing the members of a regular (not singleton) object.
    def showFruit(fruit: Fruit) {
      import fruit._
      println(name +"s are "+ color)
    }
  • Another way Scala’s imports are flexible is that they can import packages themselves, not just their non-package members.

    This is only natural if you think of nested packages being contained in their surrounding package.

    // Importing a package name.
    import java.util.regex
    
    class AStarB {
      // Accesses java.util.regex.Pattern
      val pat = regex.Pattern.compile("a*b")
    }
  • Imports in Scala can also rename or hide members.

    This is done with an import selector clause enclosed in braces, which follows the object from which members are imported.

    // imports just members Apple and Orange from object Fruits.
    import Fruits.{Apple, Orange}
    
    // imports the two members Apple and Orange from object Fruits.
    // However, the `Apple` object is renamed to `McIntosh`.
    // So this object can be  accessed with either `Fruits.Apple` or `McIntosh`.
    //  A renaming clause is always of the form “<original-name> => <new-name>”.
    import Fruits.{Apple => McIntosh, Orange}
    
    // imports all members from object `Fruits`.
    // It means the same thing as `import Fruits._`.
    import Fruits.{_}
    // import Fruits._
    
    // imports all members from object Fruits but renames Apple to McIntosh.
    import Fruits.{Apple => McIntosh, _}
    
    // imports all members of `Fruits` except `Pear`.
    // A clause of the form “<original-name> => _” excludes <original-name> from the names
    //  that are imported.
    // In a sense, renaming something to ‘_’ means hiding it altogether.
    // This is useful to avoid ambiguities.
    import Fruits.{Pear => _, _}
    
    // import all Notebooks and all Fruits except for Apple.
    import Notebooks._
    import Fruits.{Apple => _, _}

In summary, an import selector can consist of the following:

  • A simple name x.

    This includes x in the set of imported names.

  • A renaming clause x => y.

    This makes the member named x visible under the name y.

  • A hiding clause x => _.

    This excludes x from the set of imported names.

  • A catch-all ‘_’.

    This imports all members except those members mentioned in a preceding clause.

    If a catch-all is given, it must come last in the list of import selectors.

  • The simpler import clauses can be seen as special abbreviations of import clauses with a selector clause.

    For example, “import p._” is equivalent to “import p.{_}” and “import p.n” is equivalent to “import p.{n}”.

13.2. Implicit imports

Scala adds some imports implicitly to every program. In essence, it is as if the following three import clauses had been added to the top of every source file with extension “.scala”:

import java.lang._ // everything in the java.lang package
import scala._     // everything in the scala package
import Predef._    // everything in the Predef object
  • The java.lang package contains standard Java classes.

    It is always implicitly imported on the JVM implementation of Scala.

    The .NET implementation would import package system instead, which is the .NET analogue of java.lang.

    Because java.lang is imported implicitly, you can write Thread instead of java.lang.Thread, for instance.

  • As you have no doubt realized by now, the scala package contains the standard Scala library, with many common classes and objects.

    Because scala is imported implicitly, you can write List instead of scala.List, for instance.

  • The Predef object contains many definitions of types, methods, and implicit conversions that are commonly used on Scala programs. For example, because Predef is imported implicitly, you can write assert instead of Predef.assert.

  • The three import clauses above are treated a bit specially in that later imports overshadow earlier ones.

    For instance, the StringBuilder class is defined both in package scala and, from Java version 1.5 on, also in package java.lang.

    Because the scala import overshadows the java.lang import, the simple name StringBuilder will refer to scala.StringBuilder, not java.lang.StringBuilder.

13.3. Access modifiers

Members of packages, classes, or objects can be labeled with the access modifiers private and protected.

  • Private members

    // A member labeled private is visible only inside the class or
    //   object that contains the member definition.
    //
    // Java would permit both accesses because it lets an outer class
    //   access private members of its inner classes.
    class Outer {
    
      class Inner {
        private def f() { println("f") }
    
        class InnerMost {
          f() // OK
        }
      }
    
      (new Inner).f() // error: f is not accessible
    }
  • Protected members

    // In Scala, a protected member is only accessible from subclasses of
    //   the class in which the member is defined.
    //
    // In Java such accesses are also possible from other classes in
    //   the same package.
    package p {
      class Super {
        protected def f() { println("f") }
      }
    
      class Sub extends Super {
        f()
      }
    
      class Other {
        (new Super).f()  // error: f is not accessible
      }
    }
  • Public members

    Every member not labeled private or protected is public.

    • There is no explicit modifier for public members.

    • Such members can be accessed from anywhere.

  • Scope of protection

    Access modifiers in Scala can be augmented with qualifiers.

    • A modifier of the form private[X] or protected[X] means that access is private or protected “up to” X, where X designates some enclosing package, class or singleton object.

    • Qualified access modifiers give you very finegrained control over visibility.

      • In particular they enable you to express Java’s accessibility notions such as package private, package protected, or private up to outermost class, which are not directly expressible with simple modifiers in Scala.

      • But they also let you express accessibility rules that cannot be expressed in Java.

    package bobsrockets
    
    package navigation {
      private[bobsrockets] class Navigator {
    
        protected[navigation] def useStarChart() {}
    
        class LegOfJourney {
          private[Navigator] val distance = 100
        }
    
        private[this] var speed = 200
      }
    }
    
    package launch {
    
      import navigation._
    
      object Vehicle {
        private[launch] val guide = new Navigator
      }
    }
    Table 1. Effects of private qualifiers on LegOfJourney.distance

    no access modifier

    public access

    private[bobsrockets]

    access within outer package

    private[navigation]

    same as package visibility in Java

    private[Navigator]

    same as private in Java

    private[LegOfJourney]

    same as private in Scala

    private[this]

    access only from same object

    Such a definition is called object-private.

    Marking a member private[this] is a guarantee that it will not be seen from other objects of the same class.

    val other = new Navigator
    other.speed // this line would not compile
  • Visibility and companion objects

    In Java, static members and instance members belong to the same class, so access modifiers apply uniformly to them.

    • You have already seen that in Scala there are no static members; instead you can have a companion object that contains members that exist only once.

    • Scala’s access rules privilege companion objects and classes when it comes to private or protected accesses.

      A class shares all its access rights with its companion object and vice versa.

      In particular, an object can access all private members of its companion class, just as a class can access all private members of its companion object.

    One exception where the similarity between Scala and Java breaks down concerns protected static members.

    • A protected static member of a Java class C can be accessed in all subclasses of C.

    • By contrast, a protected member in a companion object makes no sense, as singleton objects don’t have any subclasses.

13.4. Package objects

Any kind of definition that you can put inside a class, you can also put at the top level of a package.

  • If you have some helper method you’d like to be in scope for an entire package, go ahead and put it right at the top level of the package.

  • To do so, put the definitions in a package object.

  • Each package is allowed to have one package object.

  • Any definitions placed in a package object are considered members of the package itself.

  • Package objects are compiled to class files named package.class that are the located in the directory of the package that they augment.

    It’s useful to keep the same convention for source files named package.scala.

    // File in breaks/package.scala
    //
    // It's a package object, not a package.
    // The contents of the curly braces can include any definitions you like.
    package object breaks {
      def breakable(op: => Unit) {
        try {
          op
        } catch {
          case _: BreakException =>
        }
      }
    
      def break() {
        throw new BreakException()
      }
    
      final case class BreakException() extends Exception()
    }
    // File in Main.scala
    object Main {
      def main(args: Array[String]) {
        import java.io._
        import breaks._
    
        val in = new BufferedReader(new InputStreamReader(System.in))
        breakable {
          while (true) {
            println("? ")
            if (in.readLine() == "") break
          }
        }
      }
    }
    $ tree .
    .
    ├── Main.scala
    └── breaks
        └── package.scala
    
    1 directory, 2 files
    $ scalac **/*.scala
    $ tree .
    .
    ├── Main.scala
    └── breaks
        ├── package$.class
        ├── package$BreakException$.class
        ├── package$BreakException.class
        ├── package.class
        └── package.scala
    
    1 directory, 6 files
    $ scala Main.scala
    ?
    

14. Assertions and Unit Testing

Two important ways to check that the behavior of the software you write is as you expect are assertions and unit tests.

14.1. Assertions

Assertions in Scala are written as calls of a predefined method assert.

  • The expression assert(condition) throws an AssertionError if condition does not hold.

    • The expression assert(condition, explanation) tests condition, and, if it does not hold, throws an AssertionError that contains the given explanation.

      The type of explanation is Any, so you can pass any object as the explanation. The assert method will call toString on it to get a string explanation to place inside the AssertionError.

  • Assertions (and ensuring checks) can be enabled and disabled using the JVM’s -ea and -da command-line flags.

    When enabled, each assertion serves as a little test that uses the actual data encountered as the software runs.

14.2. Unit testing in Scala

You have many options for unit testing in Scala, from established Java tools, such as JUnit and TestNG, to new tools written in Scala, such as ScalaTest, specs, and ScalaCheck. In the remainder of this chapter, we’ll give you a quick tour of these tools.

15. Case Classes and Pattern Matching

If you have programmed in a functional language before, then you will probably recognize pattern matching.

Case classes are Scala’s way to allow pattern matching on objects without requiring a large amount of boilerplate.

In the common case, all you need to do is add a single case keyword to each class that you want to be pattern matchable.

  • Case classes

    Classes with a case modifier are called case classes.

    // source: Notification.scala
    abstract class Notification
    
    case class SMS(caller: String, message: String) extends Notification
    
    case class Email(sender: String, title: String, body: String) extends Notification
    
    case class VoiceRecording(contactName: String, link: String) extends Notification
    // $ scalac Notification.scala && javap -p SMS.class
    Compiled from "Notification.scala"
    public class SMS extends Notification implements scala.Product,scala.Serializable {
      // all arguments in the parameter list of a case class
      //   implicitly get a val prefix
      private final java.lang.String caller;
      private final java.lang.String message;
    
      public static scala.Option<scala.Tuple2<java.lang.String, java.lang.String>> unapply(SMS);
    
      // factory method with the name of the class
      public static SMS apply(java.lang.String, java.lang.String);
    
      public static scala.Function1<scala.Tuple2<java.lang.String, java.lang.String>, SMS> tupled();
      public static scala.Function1<java.lang.String, scala.Function1<java.lang.String, SMS>> curried();
    
      // all arguments in the parameter list are maintained as
      //   fields supports uniform access principle
      public java.lang.String caller();
      public java.lang.String message();
    
      // copy method to make a new instance of the class
      public SMS copy(java.lang.String, java.lang.String);
    
      public java.lang.String copy$default$1();
      public java.lang.String copy$default$2();
      public java.lang.String productPrefix();
      public int productArity();
      public java.lang.Object productElement(int);
      public scala.collection.Iterator<java.lang.Object> productIterator();
      public boolean canEqual(java.lang.Object);
    
      // toString, hashCode, and equals
      public int hashCode();
      public java.lang.String toString();
      public boolean equals(java.lang.Object);
    
      public SMS(java.lang.String, java.lang.String);
    }

Using the modifier makes the Scala compiler add some syntactic conveniences to your class.

  • First, it adds a factory method with the name of the class.

    scala> val sms = SMS("bob", "hello world!")
    sms: SMS = SMS(bob,hello world!)
  • The second syntactic convenience is that all arguments in the parameter list of a case class implicitly get a val prefix, so they are maintained as fields:

    scala> sms.caller
    res0: String = bob
    
    scala> sms.message
    res1: String = hello world!
  • Third, the compiler adds “natural” implementations of methods toString, hashCode, and equals to your class.

    They will print, hash, and compare a whole tree consisting of the class and (recursively) all its arguments.

    Since == in Scala always delegates to equals, this means that elements of case classes are always compared structurally:

    scala> val sms2 = SMS("bob", "hello world!")
    sms2: SMS = SMS(bob,hello world!)
    
    scala> sms == sms2
    res2: Boolean = true
  • Finally, the compiler adds a copy method to your class for making modified copies.

    This method is useful for making a new instance of the class that is the same as another one except that one or two attributes are different.

    The method works by using named and default parameters.

    You specify the changes you’d like to make by using named parameters.

    For any parameter you don’t specify, the value from the old object is used.

    scala> val sms3 = sms.copy(caller="alice")
    sms3: SMS = SMS(alice,hello world!)
    
    scala> sms == sms3
    res3: Boolean = false
  • Pattern matching

    match corresponds to switch in Java, but it’s written after the selector expression. I.e., it’s:

    selector match { alternatives }

    instead of:

    switch (selector) { alternatives }

    Pattern matching is a mechanism for checking a value against a pattern.

    A successful match can also deconstruct a value into its constituent parts.

    def showNotification(notification: Notification): String = {
      notification match {
        case SMS("012-12345", message) =>
          s"You sent an SMS to Mayor Hotline! Message: $message"
        case SMS(number, message) =>
          s"You got an SMS from $number! Message: $message"
        case Email(sender, title, _) =>
          s"You got an email from $sender with title: $title"
        case _ => s"You got an unkown message!"
      }
    }
    
    val notifications = Seq(
      Email("virus@2019-n.cov", "Drinks tonight?", "I'm free after 5!"),
      SMS("021-12345", "Mayor: Are you hungry?"),
      SMS("12345", "404: NotFound."),
      VoiceRecording("Alice", "voicerecording.org/id/123"),
      )
    
    notifications.map(showNotification).foreach(println)
    
    // Output:
    // You got an email from virus@2019-n.cov with title: Drinks tonight?
    // You got an SMS from 021-12345! Message: Mayor: Are you hungry?
    // You got an SMS from 12345! Message: 404: NotFound.
    // You got an unkown message!
  • A pattern match includes a sequence of alternatives, each starting with the keyword case.

    Each alternative includes a pattern and one or more expressions, which will be evaluated if the pattern matches.

    An arrow symbol => separates the pattern from the expressions.

  • A match expression is evaluated by trying each of the patterns in the order they are written.

    The first pattern that matches is selected, and the part following the arrow is selected and executed.

    A constant pattern like "021-12345" matches values that are equal to the constant with respect to ==.

    A variable pattern like "message", "title" matches every value. The variable then refers to that value in the right hand side of the case clause.

15.1. Kinds of patterns

15.1.1. Wildcard patterns

The wildcard pattern (_) matches any object whatsoever. You have already seen it used as a default, catch-all alternative, like this:

notification match {
  case SMS("012-12345", message) =>
    s"You sent an SMS to Mayor Hotline! Message: $message"
  case _ => s"You got an unkown message!"
}

15.1.2. Constant patterns

A constant pattern matches only itself. Any literal may be used as a constant. Also, any val or singleton object can be used as a constant.

def describe(x: Any) = x match {
  case 5 => "five"
  case true => "truth"
  case "hello" => "hi!"
  case Nil => "the empty list"
  case _ => "something else"
}

15.1.3. Variable patterns

A variable pattern matches any object, just like a wildcard. Unlike a wildcard, Scala binds the variable to whatever the object is. You can then use this variable to act on the object further.

expr match {
  case 0 => "zero"
  case somethingElse => "not zero: "+ somethingElse
}
Variable or constant?
scala> import math.{E, Pi}
import math.{E, Pi}

scala> E match {
     | case Pi => "strange match? Pi = " + Pi
     | case _ => "OK"
     | }
res0: String = OK
scala> val pi = Pi
pi: Double = 3.141592653589793

scala> E match {
     | case pi => "strange math? Pi = " + pi
     | }
res1: String = strange math? Pi = 2.718281828459045
scala> E match {
     | case pi => "strange math? Pi = " + pi
     | case _ => "OK"
     | }
<console>:15: warning: patterns after a variable pattern cannot match (SLS 8.1.1)
       case pi => "strange math? Pi = " + pi
            ^
<console>:16: warning: unreachable code due to variable pattern 'pi' on line 15
       case _ => "OK"
                 ^
<console>:16: warning: unreachable code
       case _ => "OK"
                 ^
res3: String = strange math? Pi = 2.718281828459045
// back-tick syntax for identifiers:
// 1. treat a lowercase identifier as a constant in a pattern match
// 2. treat a keyword as an ordinary identifier,
//    e.g., writing Thread.`yield`() treats yield as an identifier rather than a keyword.
scala> E match {
     | case `pi` => "strange math? Pi = " + pi
     | case _ => "OK"
     | }
res4: String = OK

15.1.4. Constructor patterns

These extra patterns mean that Scala patterns support deep matches. Such patterns not only check the top-level object supplied, but also check the contents of the object against further patterns. Since the extra patterns can themselves be constructor patterns, you can use them to check arbitrarily deep into an object.

abstract class Geometry

case class Point(x: Int, y: Int) extends Geometry

case class Line(p1: Point, p2: Point) extends Geometry

val p1 = Point(1, 2)
val p2 = Point(3, 45)
val line = Line(p1, p2)

val slope = line match {
  case Line(Point(x1, y1), Point(x2, y2)) => (y2 - y1) * 1.0 / (x2 - x1)
}

println(s"The slope of line ${line} is ${slope}.")

15.1.5. Sequence patterns

You can match against sequence types like List or Array just like you match against case classes. Use the same syntax, but now you can specify any number of elements within the pattern.

// A sequence pattern with a fixed length.
expr match {
  case List(0, _, _) => println("found it")
  case _ =>
}

If you want to match against a sequence without specifying how long it can be, you can specify _* as the last element of the pattern.

// A sequence pattern with an arbitrary length.
expr match {
  case List(0, _*) => println("found it")
  case _ =>
}

15.1.6. Tuple patterns

You can match against tuples, too. A pattern like (a, b, c) matches an arbitrary 3-tuple.

expr match {
  case (a, b, c)  =>  println("matched "+ a + b + c)
  case _ =>
}

15.1.7. Typed patterns

You can use a typed pattern as a convenient replacement for type tests and type casts.

def generalSize(x: Any) = x match {
  case s: String => s.length
  case m: Map[_, _] => m.size
  case _ => -1
}

An equivalent but more long-winded way that achieves the effect of a match against a typed pattern employs a type test followed by a type cast. Scala uses a different syntax than Java for these. To test whether an expression expr has type String, say, you write:

expr.isInstanceOf[String]

To cast the same expression to type String, you use:

expr.asInstanceOf[String]
// Using isInstanceOf and asInstanceOf (poor style).
def generalSize2(x: Any) = {
  if (x.isInstanceOf[String]) {
    val s = x.asInstanceOf[String]
    s.length
  } else if (x.isInstanceOf[Map[_, _]]) {
    val m = x.asInstanceOf[Map[_, _]]
    m.size
  } else {
    -1
  }
}
  • Type erasure

    Can you also test for a map with specific element types?

    scala> def isIntIntMap(x: Any) = x match {
         | case m: Map[Int, Int] => true
         | case _ => false
         | }
    <console>:12: warning: non-variable type argument Int in type pattern  \
    scala.collection.immutable.Map[Int,Int] (the underlying of Map[Int,Int]) is unchecked \
    since it is eliminated by erasure
           case m: Map[Int, Int] => true
                   ^
    isIntIntMap: (x: Any)Boolean

Scala uses the erasure model of generics, just like Java does.

  • This means that no information about type arguments is maintained at runtime.

  • Consequently, there is no way to determine at runtime whether a given Map object has been created with two Int arguments, rather than with arguments of different types.

    All the system can do is determine that a value is a Map of some arbitrary type parameters.

    scala> isIntIntMap(Map(1 -> 1))
    res0: Boolean = true
    
    scala> isIntIntMap(Map("abc" -> "abc"))
    res1: Boolean = true

The only exception to the erasure rule is arrays, because they are handled specially in Java as well as in Scala. The element type of an array is stored with the array value, so you can pattern match on it.

scala> def isStringArray(x: Any) = x match {
     | case a: Array[String] => "yes"
     | case _ => "no"
     | }
isStringArray: (x: Any)String

scala> val as = Array("abc")
as: Array[String] = Array(abc)

scala> isStringArray(as)
res2: String = yes

scala> val ai = Array(1, 2, 3, 4, 5)
ai: Array[Int] = Array(1, 2, 3, 4, 5)

scala> isStringArray(ai)
res3: String = no

15.1.8. Variable binding

In addition to the standalone variable patterns, you can also add a variable to any other pattern.

You simply write the variable name, an at sign (@), and then the pattern.

  • This gives you a variable-binding pattern.

  • The meaning of such a pattern is to perform the pattern match as normal, and if the pattern succeeds, set the variable to the matched object just as with a simple variable pattern.

    // a pattern with a variable binding (via the @ sign).
    expr match {
      case UnOp("abs", e @ UnOp("abs", _)) => e
      case _ =>
    }

15.2. Pattern guards

A pattern guard comes after a pattern and starts with an if.

  • The guard can be an arbitrary boolean expression, which typically refers to variables in the pattern.* If a pattern guard is present, the match succeeds only if the guard evaluates to true.

// match only positive integers
case n: Int if 0 < n => ...

// match only strings starting with the letter ‘a’
case s: String if s(0) == 'a' => ...

15.3. Sealed classes

A sealed class cannot have any new subclasses added except the ones in the same file.

  • This is very useful for pattern matching, because it means you only need to worry about the subclasses you already know about.

  • What’s more, you get better compiler support as well.

    • If you match against case classes that inherit from a sealed class, the compiler will flag missing combinations of patterns with a warning message.

  • Therefore, if you write a hierarchy of classes intended to be pattern matched, you should consider sealing them. Simply put the sealed keyword in front of the class at the top of the hierarchy.

// source: Notification.scala
sealed abstract class Notification

case class SMS(caller: String, message: String) extends Notification

case class Email(sender: String, title: String, body: String) extends Notification

case class VoiceRecording(contactName: String, link: String) extends Notification

def showNotification(notification: Notification): String = {
  // warning: match may not be exhaustive.
  // It would fail on the following input: VoiceRecording(_, _)
  //   notification match {
  //   ^
  // one warning found
  notification match {
    case SMS("012-12345", message) =>
      s"You sent an SMS to Mayor Hotline! Message: $message"
    case Email(sender, title, _) =>
      s"You got an email from $sender with title: $title"
    // case _ => s"You got an unkown message!" // "catch all" case.
  }
}

15.4. The Option type

Scala has a standard type named Option for optional values. Such a value can be of two forms.

  • It can be of the form Some(x) where x is the actual value.

  • Or it can be the None object, which represents a missing value.

    scala> val capitals = Map("Japan" -> "Tokyo", "West Korea" -> "평양")
    capitals: scala.collection.immutable.Map[String,String] = Map(Japan -> Tokyo, West Korea -> 평양)
    
    scala> capitals.get("West Korea")
    res6: Option[String] = Some(평양)
    
    scala> capitals("West Korea")
    res5: String = 평양
    
    scala> capitals.get("PRC.")
    res7: Option[String] = None
    
    scala> capitals("PRC.")
    java.util.NoSuchElementException: key not found: PRC.
      at scala.collection.immutable.Map$Map2.apply(Map.scala:227)
      ... 28 elided

15.5. Patterns everywhere

Patterns are allowed in many parts of Scala, not just in standalone match expressions.

15.5.1. Patterns in variable definitions

scala> val myTuple = ("12345", "Mayor Hotline")
myTuple: (String, String) = (12345,Mayor Hotline)

scala> val (number, string) = myTuple
number: String = 12345
string: String = Mayor Hotline

scala> case class Point(x: Int, y: Int)
defined class Point

scala> val p = Point(12, 345)
p: Point = Point(12,345)

scala> val Point(x, y) = p
x: Int = 12
y: Int = 345

scala> case class Line(p1: Point, p2: Point)
defined class Line

scala> val myLine = Line(Point(1, 2), Point(3, 45))
myLine: Line = Line(Point(1,2),Point(3,45))

scala> val Line(Point(x1, y1), Point(x2, y2)) = myLine
x1: Int = 1
y1: Int = 2
x2: Int = 3
y2: Int = 45

15.5.2. Case sequences as partial functions

A sequence of cases (i.e., alternatives) in curly braces can be used anywhere a function literal can be used.

  • Essentially, a case sequence is a function literal, only more general.

  • Instead of having a single entry point and list of parameters, a case sequence has multiple entry points, each with their own list of parameters.

  • Each case is an entry point to the function, and the parameters are specified with the pattern.

  • The body of each entry point is the right-hand side of the case.

    // Option[Int] => Int
    scala> val withDefault: Option[Int] => Int = {
         | case Some(x) => x
         | case None => 0
         | }
    withDefault: Option[Int] => Int = $Lambda$1658/0x0000000801246000@24c7f52b
    
    scala> withDefault(Some(10))
    res0: Int = 10
    
    scala> withDefault(None)
    res1: Int = 0

One other generalization is worth noting: a sequence of cases gives you a partial function. If you apply such a function on a value it does not support, it will generate a runtime exception.

scala> val second: List[Int] => Int = {
     | case x :: y :: _ => y
     | }
<console>:11: warning: match may not be exhaustive.
It would fail on the following inputs: List(_), Nil
       val second: List[Int] => Int = {
                                      ^
second: List[Int] => Int = $Lambda$1684/0x0000000801248000@1e65f2d9

scala> second(List(12, 3, 45))
res2: Int = 3

scala> second(List())
scala.MatchError: List() (of class scala.collection.immutable.Nil$)
  at .$anonfun$second$1(<console>:11)
  at .$anonfun$second$1$adapted(<console>:11)
  ... 28 elided

Here is the second function again, this time written with a partial function type:

scala> val second: PartialFunction[List[Int], Int] = {
     | case x :: y :: _ => y
     | }
second: PartialFunction[List[Int],Int] = <function1>

scala> second.isDefinedAt(List(12, 3, 45))
res5: Boolean = true

scala> second.isDefinedAt(List())
res6: Boolean = false

15.5.3. Patterns in for expressions

scala> val capitals = Map("Japan" -> "Tokyo", "West Korea" -> "평양")
capitals: scala.collection.immutable.Map[String,String] = Map(Japan -> Tokyo, West Korea -> 평양)

// capitals yields a sequence of pairs, so you
// can be sure that every generated pair can be
//  matched against a pair pattern.
scala> for ((country, city) <- capitals)
     | println("The capital of "+ country +" is "+ city)
The capital of Japan is Tokyo
The capital of West Korea is 평양
scala> val results = List(Some("apple"), None, Some("orange"))
results: List[Option[String]] = List(Some(apple), None, Some(orange))

// a pattern possiable might not match a generated value.
scala> for (Some(fruit) <- results) println(fruit)
apple
orange

16. Working with Lists

// Lists are quite similar to arrays, but there are two important differences.
//
// First, lists are immutable.
//   That is, elements of a list cannot be changed by assignment.
// Second, lists have a recursive structure (i.e., a linked list), whereas arrays are flat.
val fruit = List("apples", "oranges", "pears")
val nums = List(1, 2, 3, 4)
val diag3 =
  List(
    List(1, 0, 0),
    List(0, 1, 0),
    List(0, 0, 1)
  )
val empty = List() // Nil

// Like arrays, lists are homogeneous: the elements of a list all have the same type.
// The type of a list that has elements of type T is written `List[T]`.
val fruit: List[String] = List("apples", "oranges", "pears")
val nums: List[Int] = List(1, 2, 3, 4)
val empty: List[Nothing] = List()

// The list type in Scala is covariant.
// This means that for each pair of types `S` and `T`, if `S` is a subtype of `T`,
//   then `List[S]` is a subtype of `List[T]`.
// For instance, `List[String]` is a subtype of `List[Object]`.
//   This is natural because every list of strings can also be seen as a list of objects.
// So the empty list object, which has type `List[Nothing]`, can also be seen as an object
//   of every other list type of the form `List[T]`.
//
// `List()` is also of type `List[String]`!
val xs: List[String] = List()

// All lists are built from two fundamental building blocks, `Nil` and `::` (pronounced “cons”).
// Nil represents the empty list.
// The infix operator, `::`, expresses list extension at the front.
// That is, `x :: xs` represents a list whose first element is `x`, followed
//   by (the elements of) list `xs`.
val fruit = "apples" :: ("oranges" :: ("pears" :: Nil))
val nums = 1::(2::(3::(4::Nil)))
val diag3 = (1 :: (0 :: (0 :: Nil))) ::
            (0 :: (1 :: (0 :: Nil))) ::
            (0 :: (0 :: (1 :: Nil))) :: Nil
val empty = Nil

// Because it ends in a colon, the `::` operation associates to the right:
//   `A :: B :: C` is interpreted as A :: (B :: C).
// Therefore, you can drop the parentheses in the previous definitions. For instance:
val nums = 1 :: 2 :: 3 :: 4 :: Nil
// is equivalent to the previous definition of nums.
// insertion sort
def isort(xs: List[Int]): List[Int] =
  if (xs.isEmpty) Nil
  else insert(xs.head, isort(xs.tail))

def insert(x: Int, xs: List[Int]): List[Int] =
  if (xs.isEmpty || x <= xs.head) x :: xs
  else xs.head :: insert(x, xs.tail)

scala> val hotline = isort(List(3, 2, 1, 5, 4)).mkString("")
hotline: String = 12345

16.1. List patterns

Lists can also be taken apart using pattern matching.

  • List patterns correspond one-by-one to list expressions.

  • You can either match on all elements of a list using a pattern of the form List(…​),

    scala> val List(a, b, c) = fruit
    a: String = apples
    b: String = oranges
    c: String = pears
  • or you take lists apart bit by bit using patterns composed from the :: operator and the Nil constant.

    scala> val a :: b :: rest = fruit
    a: String = apples
    b: String = oranges
    rest: List[String] = List(pears)
    // insertion sort using pattern matching
    def isort(xs: List[Int]): List[Int] = xs match {
      case Nil => Nil
      case x :: rs => insert(x, isort(rs))
    }
    
    def insert(x: Int, xs: List[Int]): List[Int] = xs match {
      case Nil => x :: Nil
      case y :: ys => if (x <= y) x :: xs
                      else y :: insert(x, ys)
    }

16.2. First-order methods on class List

A method is first-order if it does not take any functions as arguments.

16.2.1. Concatenating two lists

An operation similar to :: is list concatenation, written :::.

  • Unlike ::, ::: takes two lists as operands.

  • The result of xs:::ys is a new list that contains all the elements of xs, followed by all the elements of ys.

  • Like cons, list concatenation associates to the right.

    scala> List(1) :: List(2) :: List(3) :: Nil
    res0: List[List[Int]] = List(List(1), List(2), List(3))
    
    scala> List(1) :: List(2) :: List(3)
    res1: List[Any] = List(List(1), List(2), 3)
    
    scala> List(1) ::: List(2) ::: List(3)
    res2: List[Int] = List(1, 2, 3)
    
    scala> List(1) ::: ( List(2) ::: List(3) )
    res3: List[Int] = List(1, 2, 3)
    // The Divide and Conquer principle
    //
    // Concatenation (:::) is implemented as a method in class List.
    // It would also be possible to implement concatenation "by hand",
    //   using pattern matching on lists.
    //
    // def append[T](xs: List[T], xy: List[T]): List[T]
    //
    // To design the implementation of append, it pays to remember
    //   the "divide and conquer" design principle for programs
    //   over recursive data structures such as lists.
    //
    // Many algorithms over lists first split an input list into simpler cases
    //   using a pattern match.
    //   That's the *divide* part of the principle.
    // They then construct a result for each case.
    //   If the result is a non-empty list, some of its parts may be constructed
    //   by recursive invocations of the same algorithm.
    //   That's the *conquer* part of the principle.
    
    def append[T](xs: List[T], xy: List[T]): List[T] = xs match {
      case List() => xy
      case x ::  xs1 => x :: append(xs1, xy)
    }

16.2.2. Taking the length of a list: length

The length method computes the length of a list.

  • On lists, unlike arrays, length is a relatively expensive operation.

  • It needs to traverse the whole list to find its end and therefore takes time proportional to the number of elements in the list.

    scala> List(1, 2, 3).length
    res3: Int = 3

16.2.3. Accessing the end of a list: init and last

Unlike head and tail, which both run in constant time, init and last need to traverse the whole list to compute their result. They therefore take time proportional to the length of the list.

scala> val abcde = List('a', 'b', 'c', 'd', 'e')
abcde: List[Char] = List(a, b, c, d, e)

scala> abcde.last
res4: Char = e

scala> abcde.init
res5: List[Char] = List(a, b, c, d)

scala> List().init
java.lang.UnsupportedOperationException: empty.init
  at scala.collection.TraversableLike.init(TraversableLike.scala:647)
  at scala.collection.TraversableLike.init$(TraversableLike.scala:646)
  at scala.collection.AbstractTraversable.init(Traversable.scala:108)
  ... 28 elided

scala> List().last
java.util.NoSuchElementException
  at scala.collection.LinearSeqOptimized.last(LinearSeqOptimized.scala:150)
  at scala.collection.LinearSeqOptimized.last$(LinearSeqOptimized.scala:149)
  at scala.collection.immutable.List.last(List.scala:91)
  ... 28 elided

16.2.4. Reversing lists: reverse

scala> abcde.reverse
res6: List[Char] = List(e, d, c, b, a)

// reverse using concatenation(:::):
// the total complexity of rev is:
//   n + (n − 1) + ... + 1 = (1 + n) ∗ n / 2
def rev[T](xs: List[T]): List[T] = xs match {
  case List() => xs
  case x :: xs1 => rev(xs1) ::: List(x)
}

16.2.5. Prefixes and suffixes: drop, take, and splitAt

// equals: List(abcde.head)
scala> abcde.take(1)
res15: List[Char] = List(a)

scala> abcde.take(2)
res17: List[Char] = List(a, b)

// equals: abcde.tail
scala> abcde.drop(1)
res18: List[Char] = List(b, c, d, e)

// equals: List(abcde.take(2), abcde.drop(2))
scala> abcde.splitAt(2)
res21: (List[Char], List[Char]) = (List(a, b),List(c, d, e))

16.2.6. Element selection: apply and indices

scala> abcde.apply(2) // rare in Scala
res31: Char = c

scala> abcde(2)       // rare in Scala
res32: Char = c

scala> abcde.indices
res33: scala.collection.immutable.Range = Range 0 until 5

16.2.7. Flattening a list of lists: flatten

The flatten method takes a list of lists and flattens it out to a single list:

scala> List(List(1, 2), List(3), List(), List(4, 5)).flatten
res14: List[Int] = List(1, 2, 3, 4, 5)
scala> fruit.map(_.toCharArray).flatten
res15: List[Char] = List(a, p, p, l, e, s, o, r, a, n, g, e, s, p, e, a, r, s)

16.2.8. Zipping lists: zip and unzip

// The `zip` operation takes two lists and forms a list of pairs:
scala> abcde.indices zip abcde
res34: scala.collection.immutable.IndexedSeq[(Int, Char)] = Vector((0,a), (1,b), (2,c), (3,d), (4,e))

// If the two lists are of different length, any unmatched elements are dropped:
scala> val zipped = abcde zip List(1, 2, 3)
zipped: List[(Char, Int)] = List((a,1), (b,2), (c,3))

// A useful special case is to zip a list with its index.
scala> abcde.zipWithIndex
res18: List[(Char, Int)] = List((a,0), (b,1), (c,2), (d,3), (e,4))

// Any list of tuples can also be changed back to a tuple of lists by using the `unzip` method:
scala> abcde.zipWithIndex.unzip
res38: (List[Char], List[Int]) = (List(a, b, c, d, e),List(0, 1, 2, 3, 4))

16.2.9. Displaying lists: toString and mkString

scala> abcde.toString
res20: String = List(a, b, c, d, e)

scala> abcde mkString ("[", ",", "]")
res21: String = [a,b,c,d,e]

scala> abcde mkString ""
res22: String = abcde

scala> abcde.mkString
res23: String = abcde

scala> abcde mkString ("List(", ", ", ")")
res24: String = List(a, b, c, d, e)

// There are also variants of the `mkString` methods called `addString` which append
//   the constructed string to a `scala.StringBuilder` object, rather than returning
//   them as a result:
scala> val buf = new StringBuilder
buf: StringBuilder =

scala> abcde addString (buf, "(", ";", ")")
res42: StringBuilder = (a;b;c;d;e)

16.2.10. Converting lists: iterator, toArray, copyToArray

// To convert data between the flat world of arrays and the recursive world of
//   lists, you can use method `toArray` in class List and `toList` in class Array:
scala> val arr = abcde.toArray
arr: Array[Char] = Array(a, b, c, d, e)

scala> arr.toList
res43: List[Char] = List(a, b, c, d, e)

// There's also a method `copyToArray`, which copies list elements to successive array
//   positions within some destination array.
scala> val arr2 = new Array[Int](10)
arr2: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

scala> List(1, 2, 3, 4, 5).copyToArray(arr2, 3)

scala> arr2
res47: Array[Int] = Array(0, 0, 0, 1, 2, 3, 4, 5, 0, 0)

scala> List(1, 2, 3, 4, 5).copyToArray(arr2, 8)

scala> arr2
res49: Array[Int] = Array(0, 0, 0, 1, 2, 3, 4, 5, 1, 2)

// Finally, if you need to access list elements via an iterator, you
//   can use the `iterator` method:
scala> val it = List(1, 23, 45).iterator
it: Iterator[Int] = <iterator>

scala> it.next
res56: Int = 1

scala> it.next
res57: Int = 23

scala> it.next
res58: Int = 45

scala> it.hasNext
res59: Boolean = false

scala> it.next
java.util.NoSuchElementException: next on empty iterator
  at scala.collection.Iterator$$anon$2.next(Iterator.scala:41)
  at scala.collection.Iterator$$anon$2.next(Iterator.scala:39)
  at scala.collection.LinearSeqLike$$anon$1.next(LinearSeqLike.scala:50)
  ... 28 elided

16.2.11. Example: Merge sort

// Merge sort works as follows:
// First, if the list has zero or one elements, it is already sorted,
//   so the list can be returned unchanged.
// Longer lists are split into two sub-lists, each containing about half
//   the elements of the original list.
//   Each sub-list is sorted by a recursive call to the sort function,
//   and the resulting two sorted lists are then combined in a merge operation.
//
// For a general implementation of merge sort, you want to leave open the type
//   of list elements to be sorted, and also want to leave open the function
//   to be used for the comparison of elements.
def msort[T](less: (T, T) => Boolean)(xs: List[T]): List[T] = {

  def merge(xs: List[T], ys: List[T]): List[T] = {
    (xs, ys) match {
      case (Nil, _) => ys
      case (_, Nil) => xs
      case (x :: xs1, y :: ys1) => {
        if (less(x, y)) x :: merge(xs1, ys)
        else y :: merge(xs, ys1)
      }
    }
  }

  val n = xs.length / 2
  if (n == 0) {
    xs
  } else {
    val (ys, zs) = xs.splitAt(n)
    merge(msort(less)(ys), msort(less)(zs))
  }
}

val nums = List(4, 2, 1, 5, 3)
val hotline = msort[Int]((x, y) => x < y)(nums).mkString
println(hotline)
// 12345

16.3. Higher-order methods on class List

16.3.1. Mapping over lists: map, flatMap and foreach

// map
scala> List(0, 1, 2, 3, 4).map(_ + 1)
res6: List[Int] = List(1, 2, 3, 4, 5)

scala> val words = List("eht", "royam", "eniltoh")
words: List[String] = List(eht, royam, eniltoh)

scala> words.map(_.toList.reverse.mkString).map(_.capitalize)
res7: List[String] = List(The, Mayor, Hotline)

// flatMap
scala> words.flatMap(_.toList.reverse)
res8: List[Char] = List(t, h, e, m, a, y, o, r, h, o, t, l, i, n, e)

scala> words.map(_.toList.reverse)
res9: List[List[Char]] = List(List(t, h, e), List(m, a, y, o, r), List(h, o, t, l, i, n, e))

scala> var sum = 0
sum: Int = 0

// foreach
scala> List(0, 1, 2, 3, 4).map(_ + 1).foreach(sum += _)

scala> sum
res14: Int = 15

16.3.2. Filtering lists: filter, partition, find, takeWhile, dropWhile, and span

scala> List(1, 2, 3, 4, 5).filter(_ % 2 == 0)
res0: List[Int] = List(2, 4)

// The `partition` method is like `filter`, but it returns a pair of lists.
// One list contains all elements for which the predicate is true, while
// the other list contains all elements for which the predicate is false.
// It is defined by the equality:
//   `xs partition p equals (xs filter p, xs filter (!p(_)))`
scala> List(1, 2, 3, 4, 5).partition(_ % 2 == 0)
res2: (List[Int], List[Int]) = (List(2, 4),List(1, 3, 5))

// The `find` method is also similar to `filter` but
// it returns the first element satisfying a given predicate, rather than
// all such elements.
// The operation `xs find p` takes a list `xs` and a predicate `p` as operands.
// It returns an `optional` value. If there is an element `x` in `xs` for
// which `p(x)` is true, `Some(x)` is returned.
// Otherwise, `p` is false for all elements, and `None` is returned.
scala> List(1, 2, 3, 4, 5).find(_ % 2 == 0)
res3: Option[Int] = Some(2)

// The `takeWhile` and `dropWhile` operators also take a predicate as their right operand.
// The operation `xs takeWhile p` takes the longest prefix of list `xs` such that every
// element in the prefix satisfies `p`.
// Analogously, the operation `xs dropWhile p` removes the longest prefix from list `xs`
// such that every element in the prefix satisfies `p`.
scala> List(1, 2, 3, -4, 5).takeWhile(_ > 0)
res4: List[Int] = List(1, 2, 3)

scala> List(1, 2, 3, -4, 5).takeWhile(_ < 0)
res5: List[Int] = List()

scala> List(1, 2, 3, -4, 5).dropWhile(_ > 0)
res6: List[Int] = List(-4, 5)

scala> List(1, 2, 3, -4, 5).dropWhile(_ < 0)
res7: List[Int] = List(1, 2, 3, -4, 5)

// The `span` method combines `takeWhile` and `dropWhile` in one operation,
// just like `splitAt` combines `take` and `drop`.
// It returns a pair of two lists, defined by the equality:
//   `xs span p equals (xs takeWhile p, xs dropWhile p)`
scala> List(1, 2, 3, -4, 5).span(_ > 0)
res8: (List[Int], List[Int]) = (List(1, 2, 3),List(-4, 5))

scala> List(1, 2, 3, -4, 5).span(_ < 0)
res9: (List[Int], List[Int]) = (List(),List(1, 2, 3, -4, 5))

16.3.3. Predicates over lists: forall and exists

// The operation `xs forall p` takes as arguments a list `xs` and a predicate `p`.
//   Its result is `true` if all elements in the list satisfy `p`.
// Conversely, the operation `xs exists p` returns `true` if there is an element
//   in `xs` that satisfies the predicate `p`.
scala> List(1, 2, 3, -4, 5).forall(_ > 0)
res10: Boolean = false

scala> List(1, 2, 3, -4, 5).exists(_ < 0)
res11: Boolean = true

16.3.4. Folding lists: foldLeft, /: and foldRight, :\

// A fold left operation `(z /: xs) (op)` involves three objects:
//   a start value `z`,
//   a list `xs`,
//   and a binary operation `op`.
// The result of the fold is `op` applied between successive elements
//   of the list prefixed by `z`.
scala> val nums = List(1, 2, 3, -4, 5)
nums: List[Int] = List(1, 2, 3, -4, 5)

scala> (0 /: nums)(_ + _)
<console>:13: warning: method /: in trait TraversableOnce is deprecated (since 2.12.10): Use foldLeft instead of /:
       (0 /: nums)(_ + _)
          ^
res13: Int = 7

scala> nums.foldLeft(0)(_ + _)
res16: Int = 7

// The `:\` operator is pronounced fold right.
// It involves the same three operands as fold left, but the first two appear
//   in reversed order:
//   The first operand is the list to fold,
//   the second is the start value.
scala> (nums :\ 0)(_ + _)
<console>:13: warning: method :\ in trait TraversableOnce is deprecated (since 2.12.10): Use foldRight instead of :\
       (nums :\ 0)(_ + _)
             ^
res19: Int = 7

scala> nums.foldRight(0)(_ + _)
res20: Int = 7
// reverse using concatenation(:::)
// running time was quadratic in the length of the list to be reversed.
def rev[T](xs: List[T]): List[T] = xs match {
  case List() => xs
  case x :: xs1 => rev(xs1) ::: List(x)
}

// List reversal using fold
// If you analyze the complexity of reverseLeft, you’ll
// find that it applies a constant-time operation (“snoc”) n times, where n is the
// length of the argument list. Hence, the complexity of reverseLeft is linear,
// as hoped for.
def reverseLeft[T](xs: List[T]) =
  (List[T]() /: xs) {(ys, y) => y :: ys}

16.3.5. Sorting lists: sortWith

scala> List(1, 4, 3, 2, 5).sortWith(_ < _)
res21: List[Int] = List(1, 2, 3, 4, 5)

16.4. Methods of the List object

// Creating lists from their elements: `List.apply`
scala> List(1, 2, 3, 4, 5)
res0: List[Int] = List(1, 2, 3, 4, 5)

scala> List.apply(1, 2, 3, 4, 5)
res1: List[Int] = List(1, 2, 3, 4, 5)

// Creating a range of numbers: `List.range`
scala> List.range(1, 9)
res0: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8)

scala> List.range(1, 9, 1)
res1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8)

scala> List.range(1, 9, 2)
res2: List[Int] = List(1, 3, 5, 7)

scala> List.range(9, -1, -2)
res3: List[Int] = List(9, 7, 5, 3, 1)

// Creating uniform lists: `List.fill`
scala> List.fill(5)('a')
res4: List[Char] = List(a, a, a, a, a)

scala> List.fill(5)("hello")
res5: List[String] = List(hello, hello, hello, hello, hello)

scala> List.fill(2, 3)('b')
res7: List[List[Char]] = List(List(b, b, b), List(b, b, b))

// Tabulating a function: `List.tabulate`
scala> List.tabulate(3)(n => n * n)
res0: List[Int] = List(0, 1, 4)

scala> List.tabulate(3, 3)(_ * _)
res1: List[List[Int]] = List(List(0, 0, 0), List(0, 1, 2), List(0, 2, 4))

// Concatenating multiple lists: `List.concat`
scala> List.concat(List('a', 'b'), List('c'))
res2: List[Char] = List(a, b, c)

scala> List.concat(List('a', 'b'), List('c'), List())
res3: List[Char] = List(a, b, c)

scala> List.concat()
res4: List[Nothing] = List()

16.5. Understanding Scala’s type inference algorithm

def msort[T](less: (T, T) => Boolean)(xs: List[T]): List[T] = {

  def merge(xs: List[T], ys: List[T]): List[T] = {
    (xs, ys) match {
      case (Nil, _) => ys
      case (_, Nil) => xs
      case (x :: xs1, y :: ys1) => {
        if (less(x, y)) x :: merge(xs1, ys)
        else y :: merge(xs, ys1)
      }
    }
  }

  val seq = xs.length / 2
  if (seq == 0) xs
  else {
    val (ys, zs) = xs.splitAt(seq)
    merge(msort(less)(ys), msort(less)(zs))
  }
}
scala> val nums = List(1, 5, 4, 2, 3)
nums: List[Int] = List(1, 5, 4, 2, 3)

// a longer form of comparison function with named parameters and explicit types
scala> msort((x: Int, y: Int) => x < y)(nums)
res1: List[Int] = List(1, 2, 3, 4, 5)

scala> nums.sortWith((x: Int, y: Int) => x < y)
res2: List[Int] = List(1, 2, 3, 4, 5)
// a concise form, `(_ > _)`, where named parameters are replaced by underscores
scala> nums.sortWith(_ < _)
res3: List[Int] = List(1, 2, 3, 4, 5)

scala> msort(_ < _)(nums)
<console>:14: error: missing parameter type for expanded function ((x$1: <error>, x$2) => x$1.$less(x$2))
       msort(_ < _)(nums)
             ^
Type inference in Scala is flow based.
  • In a method application m(args),

    the inferencer first checks whether the method m has a known type. If it has, that type is used to infer the expected type of the arguments.

    For instance, in nums.sortWith(_ > \_), the type of nums is List[Int], hence sortWith is known to be a method that takes an argument of type (Int, Int) => Boolean and produces a result of type List[Int].

    Since the parameter types of the function arguments are thus known, they need not be written explicitly. With what it knows about sortWith, the inferencer can deduce that (_ > _) should expand to ((x: Int, y: Int) => x > y) where x and y are some arbitrary fresh names.

  • Now consider the second case, msort(_ > _)(nums).

    The type of msort is a curried, polymorphic method type that takes an argument of type (T, T) => Boolean to a function from List[T] to List[T] where T is some as-yet unknown type.

    The msort method needs to be instantiated with a type parameter before it can be applied to its arguments.

    Because the precise instance type of msort in the application is not yet known, it cannot be used to infer the type of its first argument.

  • The type inferencer changes its strategy in this case; it first type checks method arguments to determine the proper instance type of the method.

    However, when tasked to type check the shorthand function literal, (_ > _), it fails because it has no information about the types of the implicit function parameters that are indicated by underscores.

  • One way to resolve the problem is to pass an explicit type parameter to msort, as in:

    scala> msort[Int](_ < _)(nums)
    res6: List[Int] = List(1, 2, 3, 4, 5)

    Because the correct instance type of msort is now known, it can be used to infer the type of the arguments.

  • Another possible solution is to rewrite the msort method so that its parameters are swapped:

    // What has happened is that the inferencer used the known type of the first parameter `nums`
    // to determine the type parameter of `msortSwapped`.
    // Once the precise type of `msortSwapped` was known, it could be used in turn to infer the
    //   type of the second parameter, `(_ > _)`.
    //
    // Generally, when tasked to infer the type parameters of a polymorphic method, the type inferencer
    //    consults the types of all value arguments in the first parameter list but no arguments beyond that.
    //  Since `msortSwapped` is a curried method with two parameter lists, the second argument (i.e., the function value) did not need to be consulted to determine the type parameter of the method.
    
    def msortSwapped[T](xs: List[T])(less: (T, T) => Boolean): List[T] = msort(less)(xs)
    scala> msortSwapped(nums)(_ < _)
    res1: List[Int] = List(1, 2, 3, 4, 5)

This inference scheme suggests the following library design principle:

When designing a polymorphic method that takes some non-function arguments and a function argument, place the function argument last in a curried parameter list by its own. That way, the method’s correct instance type can be inferred from the non-function arguments, and that type can in turn be used to type check the function argument. The net effect is that users of the method will be able to give less type information and write function literals in more compact ways.

17. Collections

17.1. Sequences

  • Lists

    // Lists support fast addition and removal of items to the beginning of the list, but
    //   they do not provide fast access to arbitrary indexes because the implementation
    //   must iterate through the list linearly.
    scala> val colors = List("red", "blue", "green")
    colors: List[String] = List(red, blue, green)
    
    scala> colors.head
    res0: String = red
    
    scala> colors.tail
    res1: List[String] = List(blue, green)
  • Arrays

    // Arrays allow you to hold a sequence of elements and efficiently access an element
    //   at an arbitrary position, both to get or update the element, with a zero-based index.
    scala> val fiveInts = new Array[Int](5)
    fiveInts: Array[Int] = Array(0, 0, 0, 0, 0)
    
    scala> val fiveToOne = Array(5, 4, 3, 2, 1)
    fiveToOne: Array[Int] = Array(5, 4, 3, 2, 1)
    
    scala> fiveInts(0) = fiveToOne(4)
    
    scala> fiveInts
    res3: Array[Int] = Array(1, 0, 0, 0, 0)
  • List buffers

    // Class List provides fast access to the head of the list, but not the end. Thus, when
    //   you need to build a list by appending to the end, you should consider building the
    //   list backwards by prepending elements to the front, then when you're done, calling
    //   reverse to get the elements in the order you need.
    //
    // Another alternative, which avoids the reverse operation, is to use a `ListBuffer`.
    //   A ListBuffer is a mutable object (contained in `package scala.collection.mutable`), which
    //   can help you build lists more efficiently when you need to append.
    //
    // ListBuffer provides constant time append and prepend operations.
    //   You append elements with the `+=` operator,
    //   and prepend them with the `+=:` operator.
    //
    // When you're done building, you can obtain a List by invoking `toList` on the ListBuffer.
    scala> import scala.collection.mutable.ListBuffer
    import scala.collection.mutable.ListBuffer
    
    scala> val buf = new ListBuffer[Int]
    buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer()
    
    scala> buf += 1
    res0: buf.type = ListBuffer(1)
    
    scala> buf += 2
    res1: buf.type = ListBuffer(1, 2)
    
    scala> 3 +=: buf
    res2: buf.type = ListBuffer(3, 1, 2)
    
    scala> 45 +=: buf
    res3: buf.type = ListBuffer(45, 3, 1, 2)
    
    scala> buf.toList
    res4: List[Int] = List(45, 3, 1, 2)
  • Array buffers

    // An ArrayBuffer is like an array, except that you can additionally add and remove elements
    //   from the beginning and end of the sequence.
    // All Array operations are available, though they are a little slower due to a layer of wrapping
    //   in the implementation.
    // The new addition and removal operations are constant time on average, but occasionally require
    //   linear time due to the implementation needing to allocate a new array to hold the buffer's contents.
    scala> import scala.collection.mutable.ArrayBuffer
    import scala.collection.mutable.ArrayBuffer
    
    scala> val buf = new ArrayBuffer[Int]()
    buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()
    
    scala> buf += 3
    res4: buf.type = ArrayBuffer(3)
    
    scala> buf += 5
    res5: buf.type = ArrayBuffer(3, 5)
    
    scala> buf += 1
    res6: buf.type = ArrayBuffer(3, 5, 1)
    
    scala> buf -= 5
    res7: buf.type = ArrayBuffer(3, 1)

17.2. Sets and maps

  • Default map and set definitions in Predef.

    // The `type` keyword is used in `Predef` to define `Set` and `Map` as aliases
    //   for the longer fully qualified names of the immutable set and map traits.
    // The `val`s named `Set` and `Map` are initialized to refer to the singleton objects
    //   for the immutable `Set` and `Map`.
    // So `Map` is the same as `Predef.Map`, which is defined to be
    //   the same as `scala.collection.immutable.Map`.
    // This holds both for the Map type and Map object.
    object Predef {
      type Map[A, +B] = collection.immutable.Map[A, B]
      type Set[A] = collection.immutable.Set[A]
      val Map = collection.immutable.Map
      val Set = collection.immutable.Set
      // ...
    }
  • Using sets

    scala> import scala.collection.mutable
    import scala.collection.mutable
    
    scala> val text = "See Spot run. Run, Spot. Run!"
    text: String = See Spot run. Run, Spot. Run!
    
    scala> val wordsArray = text.split("[ !,.]+")
    wordsArray: Array[String] = Array(See, Spot, run, Run, Spot, Run)
    
    scala> val words = mutable.Set.empty[String]
    words: scala.collection.mutable.Set[String] = Set()
    
    scala> wordsArray.foreach(words += _)
    
    scala> words
    res1: scala.collection.mutable.Set[String] = Set(Run, Spot, run, See)
    Table 2. Common operations for sets
    What it is What it does

    val nums = Set(1, 2, 3)

    Creates an immutable set

    (nums.toString returns Set(1, 2, 3))

    nums + 5

    Adds an element

    (returns Set(1, 2, 3, 5))

    nums - 3

    Removes an element

    (returns Set(1, 2))

    nums ++ List(5, 6)

    Adds multiple elements

    (returns Set(1, 2, 3, 5, 6))

    nums -- List(1, 2)

    Removes multiple elements

    (returns Set(3))

    nums & Set(1, 3, 5, 7)

    Takes the intersection of two sets

    (returns Set(1, 3))

    nums.size

    Returns the size of the set

    (returns 3)

    nums.contains(3)

    Checks for inclusion

    (returns true)

    import scala.collection.mutable

    Makes the mutable collections easy to access

    val words = mutable.Set.empty[String]

    Creates an empty, mutable set

    (words.toString returns Set())

    words += "the"

    Adds an element

    (words.toString returns Set(the))

    words -= "the"

    Removes an element, if it exists (words.toString returns Set())

    words ++= List("do", "re", "mi")

    Adds multiple elements

    (words.toString returns Set(do, re, mi))

    words --= List("do", "re")

    Removes multiple elements

    (words.toString returns Set(mi))

    words.clear

    Removes all elements

    (words.toString returns Set())

  • Using maps

    scala> import scala.collection.mutable
    import scala.collection.mutable
    
    scala> val map = mutable.Map.empty[String, Int]
    map: scala.collection.mutable.Map[String,Int] = Map()
    
    scala> map("hello") = 1
    
    scala> map("there") = 2
    
    scala> map("hello")
    res3: Int = 1
    
    scala> val wordsCount = mutable.Map.empty[String, Int]
    wordsCount: scala.collection.mutable.Map[String,Int] = Map()
    
    scala> wordsArray.foreach(w => if (wordsCount.contains(w))  wordsCount(w) += 1 else wordsCount(w) = 1)
    
    scala> wordsCount
    res4: scala.collection.mutable.Map[String,Int] = Map(See -> 1, Spot -> 2, Run -> 2, run -> 1)
    Table 3. Common operations for maps
    What it is What it does

    val nums = Map("i" -> 1, "ii" -> 2)

    Creates an immutable map

    (nums.toString returns Map(i -> 1, ii -> 2))

    nums + ("vi" -> 6)

    Adds an entry

    (returns Map(i -> 1, ii → 2, vi -> 6))

    nums - "ii"

    Removes an entry

    (returns Map(i -> 1))

    nums ++ List("iii" -> 3, "v" -> 5)

    Adds multiple entries

    (returns Map(i -> 1, ii -> 2, iii -> 3, v -> 5))

    nums -- List("i", "ii")

    Removes multiple entries

    (returns Map())

    nums.size

    Returns the size of the map

    (returns 2)

    nums.contains("ii")

    Checks for inclusion

    (returns true)

    nums("ii")

    Retrieves the value at a specified key

    (returns 2)

    nums.keys

    Returns the keys

    (returns an Iteratable over the strings "i" and "ii")

    nums.keySet

    Returns the keys as a set

    (returns Set(i, ii))

    nums.values

    Returns the values

    (returns an Iterable over the integers 1 and 2)

    nums.isEmpty

    Indicates whether the map is empty

    (returns false)

    import scala.collection.mutable

    Makes the mutable collections easy to access

    val words = mutable.Map.empty[String, Int]

    Creates an empty, mutable map

    words += ("one" -> 1)

    Adds a map entry from "one" to 1 (words.toString returns Map(one → 1))

    words -= "one"

    Removes a map entry, if it exists (words.toString returns Map())

    words ++= List("one" -> 1, "two" -> 2, "three" -> 3)

    Adds multiple map entries

    (words.toString returns Map(one -> 1, two -> 2, three -> 3))

    words --= List("one", "two")

    Removes multiple objects

    (words.toString returns Map(three -> 3))

  • Default sets and maps

    Table 4. Default immutable set implementations
    Number of elements Implementation

    0

    scala.collection.immutable.EmptySet

    1

    scala.collection.immutable.Set1

    2

    scala.collection.immutable.Set2

    3

    scala.collection.immutable.Set3

    4

    scala.collection.immutable.Set4

    5 or more

    scala.collection.immutable.HashSet

    Table 5. Default immutable map implementations
    Number of elements Implementation

    0

    scala.collection.immutable.EmptyMap

    1

    scala.collection.immutable.Map1

    2

    scala.collection.immutable.Map2

    3

    scala.collection.immutable.Map3

    4

    scala.collection.immutable.Map4

    5 or more

    scala.collection.immutable.HashMap

  • Sorted sets and maps

    On occasion you may need a set or map whose iterator returns elements in a particular order.

    For this purpose, the Scala collections library provides traits SortedSet and SortedMap.

    These traits are implemented by classes TreeSet and TreeMap, which use a red-black tree to keep elements (in the case of TreeSet) or keys (in the case of TreeMap) in order.

    The order is determined by the Ordered trait, which the element type of the set, or key type of the map, must either mix in or be implicitly convertible to.

    scala> import scala.collection.immutable.TreeSet
    import scala.collection.immutable.TreeSet
    
    scala> val ts = TreeSet(3,2,4,5,1)
    ts: scala.collection.immutable.TreeSet[Int] = TreeSet(1, 2, 3, 4, 5)
    
    scala> import scala.collection.immutable.TreeMap
    import scala.collection.immutable.TreeMap
    
    scala> var tm = TreeMap(3 -> "2020", 2 -> "2019", 4 -> "2021", 5 -> "2022", 1 -> "covid-19")
    tm: scala.collection.immutable.TreeMap[Int,String] = Map(1 -> covid-19, 2 -> 2019, 3 -> 2020, 4 -> 2021, 5 -> 2022)
    
    scala> tm.values
    res4: Iterable[String] = TreeMap(covid-19, 2019, 2020, 2021, 2022)
    scala> import scala.collection.mutable.TreeSet
    import scala.collection.mutable.TreeSet
    
    scala> val ts = TreeSet.empty[String]
    ts: scala.collection.mutable.TreeSet[String] = TreeSet()
    
    scala> ts += "2019"
    res7: ts.type = TreeSet(2019)
    
    scala> ts += "covid-19"
    res9: ts.type = TreeSet(2019, covid-19)
    
    scala> ts += "2022"
    res10: ts.type = TreeSet(2019, 2022, covid-19)
    
    scala> ts += "2021"
    res11: ts.type = TreeSet(2019, 2021, 2022, covid-19)
    
    scala> ts += "2020"
    res12: ts.type = TreeSet(2019, 2020, 2021, 2022, covid-19)
    
    scala> ts.toSet
    res13: scala.collection.immutable.Set[String] = Set(2020, 2022, 2019, 2021, covid-19)

18. Stateful Objects

18.1. What makes an object stateful?

You can observe the principal difference between a purely functional object and a stateful one even without looking at the object’s implementation.

  • When you invoke a method or dereference a field on some purely functional object, you will always get the same result.

    // `cs.head` will always return 'a'
    val cs = List("a", "b", "c")
  • For a stateful object, on the other hand, the result of a method call or field access may depend on what operations were previously performed on the object.

    class BankAccount {
      private var bal: Int = 0
    
      def balance: Int = bal
    
      def deposit(amount: Int) {
        require(amount > 0)
        bal += amount
      }
    
      def withdraw(amount: Int): Boolean =
        if (amount > bal) false
        else {
          bal -= amount
          true
        }
    }
    scala> val account = new BankAccount
    account: BankAccount = BankAccount@a77a465
    
    scala> account.deposit(100)
    
    scala> account.withdraw(80)
    res1: Boolean = true
    
    scala> account.withdraw(80)
    res2: Boolean = false

18.2. Reassignable variables and properties

You can perform two fundamental operations on a reassignable variable: get its value or set it to a new value.

  • In libraries such as JavaBeans, these operations are often encapsulated in separate getter and setter methods, which need to be defined explicitly.

  • In Scala, every var that is a non-private member of some object implicitly defines a getter and a setter method with it. The getter of a var x is just named x, while its setter is named x_=.

    // A class with public vars.
    //
    // The field is always marked `private[this]`, which means it can be accessed only from
    //    the object that contains it.
    // The getter and setter, on the other hand, get the same visibility as the original `var`.
    //   If the var definition is `public`, so are its getter and setter,
    //    if it is `protected` they are also `protected`, and so on.
    class Time {
      var hour = 12
      var minute = 0
    }
    // $ javap -p Time.class
    // Compiled from "Time.scala"
    // public class Time {
    //   private int hour;
    //   private int minute;
    //   public int hour();
    //   public void hour_$eq(int);
    //   public int minute();
    //   public void minute_$eq(int);
    //   public Time();
    // }
  • An interesting aspect about this expansion of vars into getters and setters is that you can also choose to define a getter and a setter directly instead of defining a var.

    // How public vars are expanded into getter and setter methods.
    //
    // Scala's convention of always interpreting a variable as
    //    a pair of setter and getter methods gives you in effect
    //     the same capabilities as C# properties without
    class Time {
      private[this] var h = 12
      private[this] var m = 0
    
      def hour: Int = h
      def hour_=(x: Int) { h = x }
    
      def minute: Int = m
      def minute_=(x: Int) { m = x }
    }
    // Defining getter and setter methods directly.
    class Time {
      private[this] var h = 12
      private[this] var m = 0
    
      def hour: Int = h
      def hour_=(x: Int) {
        require(0 <= h && h < 24)
        h = x
      }
    
      def minute: Int = m
      def minute_=(x: Int) {
        require(0 <= h && h < 60)
        m = x
      }
    }
  • It is also possible, and sometimes useful, to define a getter and a setter without an associated field.

    scala> val t = new Thermometer
    t: Thermometer = 32.0F/0.0C
    
    scala> t.celsius = 100
    t.celsius: Float = 100.0
    
    scala> t
    res1: Thermometer = 212.0F/100.0C
    
    scala> t.fahrenheit = -40
    t.fahrenheit: Float = -40.0
    
    scala> t
    res2: Thermometer = -40.0F/-40.0C

19. Type Parameterization

Type parameterization allows you to write generic classes and traits.

19.1. Functional queues

A functional queue is a data structure with three operations:

  • head

    returns the first element of the queue

  • tail

    returns a queue without its first element

  • enqueue

    returns a new queue with a given element appended at the end

Unlike a mutable queue, a functional queue does not change its contents when an element is appended.

One simple approach to implement a functional queue would be to use a list as representation type.

class SlowAppendQueue[T](elems: T*) { // Not efficient
  private val list = List[T](elems: _*)
  def head = list.head
  def tail = new SlowAppendQueue(list.tail)
  def enqueue(e: T) = new SlowAppendQueue(list ::: List(e))

  override def toString() = list.mkString("SlowAppendQueue(", ", ", ")")
}

object SlowAppendQueue {
  def apply[T](elems: T*) = new SlowAppendQueue(elems: _*)
}
type Queue[T] = SlowAppendQueue[T]
val Queue = SlowAppendQueue

scala> val q = Queue(1, 2, 3)
q: SlowAppendQueue[Int] = SlowAppendQueue(1, 2, 3)

scala> val q1 = q.enqueue(4)
q1: SlowAppendQueue[List[Int]] = SlowAppendQueue(List(1, 2, 3, 4))

scala> q
res0: SlowAppendQueue[Int] = SlowAppendQueue(1, 2, 3)
// A basic functional queue.
class Queue[T](
  private val leading: List[T],
  private val tailing: List[T]
) {

  private def mirror =
    if (leading.isEmpty)
      new Queue(tailing.reverse, Nil)
    else
      this

  def head = mirror.leading.head

  def tail = {
    val q = mirror
    new Queue(q.leading.tail, q.tailing)
  }

  def enqueue(e: T): Queue[T] =
    new Queue(leading, e :: tailing)

  override def toString() = {
    val ls = leading.mkString("Queue(", ", ", "")
    val ts = if (tailing.isEmpty) ")" else tailing.reverse.mkString(",", ", ", ")")
    ls + ts
  }
}
scala> val q = new Queue(List(1, 2, 3), Nil)
q: Queue[Int] = Queue(1, 2, 3)

scala> val q1 = q.enqueue(4)
q1: Queue[Int] = Queue(1, 2, 3,4)

scala> q
res2: Queue[Int] = Queue(1, 2, 3)

19.2. Information hiding

The Queue constructor, which is globally accessible, takes two lists as parameters, where one is reversed—hardly an intuitive representation of a queue.

19.2.1. Private constructors and factory methods

In Java, you can hide a constructor by making it private.

In Scala, the primary constructor does not have an explicit definition; it is defined implicitly by the class parameters and body. Nevertheless, it is still possible to hide the primary constructor by adding a private modifier in front of the class parameter list.

// Hiding a primary constructor by making it private.
//
// The private modifier between the class name and its parameters indicates that
//   the constructor of Queue is `private`:
//     it can be accessed only from within the class itself and its `companion object`.
//
// The class name Queue is still public, so you can use it as a type, but you cannot
//   call its constructor
class Queue[T] private (
  private val leading: List[T],
  private val trailing: List[T]
)
scala> new Queue(Nil, Nil)
<console>:13: error: constructor Queue in class Queue cannot be accessed in object $iw
       new Queue(Nil, Nil)
       ^
  • One possibility is to add an auxiliary constructor, like this:

    def this() = this(Nil, Nil)
    
    def this(elems: T*) = this(elems.toList, Nil)
  • Another possibility is to add a factory method that builds a queue from such a sequence of initial elements.

    // An apply factory method in a companion object.
    object Queue {
      // constructs a queue with initial elements ‘ xs’
      def apply[T](xs: T*) = new Queue[T](xs.toList, Nil)
    }
  • An alternative: private classes

    Private constructors and private members are one way to hide the initialization and representation of a class.

    Another, more radical way is to hide the class itself and only export a trait that reveals the public interface of the class.

    // Type abstraction for functional queues.
    trait Queue[T]{
      def head: T
      def tail: Queue[T]
      def enqueue(x: T): Queue[T]
    }
    
    object Queue {
      def apply[T](xs: T*): Queue[T] =
        new QueueImpl[T](xs.toList, Nil)
    
      private class QueueImpl[T](
        private val leading: List[T],
        private val tailing: List[T]
      ) extends Queue[T] {
    
        private def mirror =
          if (tailing.isEmpty)
            new QueueImpl(tailing.reverse, Nil)
          else
            this
    
        def head: T = mirror.leading.head
    
        def tail: Queue[T] = new QueueImpl(mirror.leading.tail, mirror.tailing)
    
        def enqueue(x: T): Queue[T] = new QueueImpl(leading, x :: tailing)
      }
    }

19.3. Variance annotations

// Queue is a trait, but not a type.
trait Queue[T] { ... }
  • Queue is not a type because it takes a type parameter.

    scala> def doesNotCompile(q: Queue) {}
    <console>:12: error: trait Queue takes type parameters
           def doesNotCompile(q: Queue) {}
                                 ^

    Instead, trait Queue enables you to specify parameterized types, such as Queue[String], Queue[Int], or Queue[AnyRef]:

    scala> def doesCompile(q: Queue[AnyRef]) {}
    doesCompile: (q: Queue[AnyRef])Unit

    Thus, Queue is a trait, and Queue[String] is a type.

  • Queue is also called a type constructor, because with it you can construct a type by specifying a type parameter.

    The type constructor Queue "generates" a family of types, which includes Queue[Int], Queue[String], and Queue[AnyRef].

  • You can also say that Queue is a generic trait.

    (Classes and traits that take type parameters are "generic", but the types they generate are "parameterized", not generic.)

    The term "generic" means that you are defining many specific types with one generically written class or trait.

    For example, trait Queue defines a generic queue. Queue[Int] and Queue[String], etc., would be the specific queues.

The combination of type parameters and subtyping poses some interesting questions.

  • For example, are there any special subtyping relationships between members of the family of types generated by Queue[T]?

  • More specifically, should a Queue[String] be considered a subtype of Queue[AnyRef]?

  • Or more generally, if S is a subtype of type T, then should Queue[S] be considered a subtype of Queue[T]?

    If so, you could say that trait Queue is covariant (or "flexible") in its type parameter T.

    Or, since it just has one type parameter, you could say simply that Queues are covariant.

    scala> var any: List[Any] = Nil
    any: List[Any] = List()
    
    scala> any = List("the", "Mayor", "Hotline")
    any: List[Any] = List(the, Mayor, Hotline)
    
    scala> any = List(1, 2, 3, 4, 5)
    any: List[Any] = List(1, 2, 3, 4, 5)
  • In Scala, however, generic types have by default nonvariant (or, “rigid”) subtyping.

    scala> var q = Queue[Any]()
    q: Queue[Any] = Queue$QueueImpl@4a7645b
    
    scala> q = Queue[Int]()
    <console>:13: error: type mismatch;
     found   : Queue[Int]
     required: Queue[Any]
    Note: Int <: Any, but trait Queue is invariant in type T.
    You may wish to define T as +T instead. (SLS 4.5)
           q = Queue[Int]()
                         ^
  • Prefixing a formal type parameter with a + indicates that subtyping is covariant (flexible) in that parameter.

    trait Queue[+T]
    
    object Queue {
      def apply[T](): Queue[T] = new QueueImpl[T]()
    
      private class QueueImpl[T] extends Queue[T]
    }
    scala> var q = Queue[Any]
    q: Queue[Any] = Queue$QueueImpl@2213b9e4
    
    scala> q = Queue[Int]
    q: Queue[Any] = Queue$QueueImpl@34fb1849
    
    scala> q = Queue[String]
    q: Queue[Any] = Queue$QueueImpl@72f10a4d
  • Besides +, there is also a prefix -, which indicates contravariant subtyping.

    If Queue were defined as trait Queue[-T] { …​ }, then if T is a subtype of type S, this would imply that Queue[S] is a subtype of Queue[T] (which in the case of queues would be rather surprising!).

  • Whether a type parameter is covariant, contravariant, or nonvariant is called the parameter’s variance.

  • The + and - symbols you can place next to type parameters are called variance annotations.

    // A nonvariant (rigid) Cell class.
    class Cell[T](init: T) {
      private[this] var current = init
      def get = current
      def set(x: T) { current = x }
    }
    scala> val c1 = new Cell[String]("abc")
    c1: Cell[String] = Cell@44def1d4
    
    scala> val c2: Cell[Any] = c1
    <console>:13: error: type mismatch;
     found   : Cell[String]
     required: Cell[Any]
    Note: String <: Any, but class Cell is invariant in type T.
    You may wish to define T as +T instead. (SLS 4.5)
           val c2: Cell[Any] = c1
                               ^
    
    scala> c2.set(1)
    <console>:12: error: not found: value c2
           c2.set(1)
           ^
    
    scala> val s: String = c1.get
    s: String = abc

19.4. Checking variance annotations

// A covariant Cell class.
class Cell[+T](init: T) {
  private[this] var current = init
  def get = current
  // error: covariant type T occurs in contravariant position in type T of value x
  def set(x: T) { current = x }
}
// A contravariant Cell class.
class Cell[-T](init: T) {
  private[this] var current = init
  // error: contravariant type T occurs in covariant position in type => T of method get
  def get = current
  def set(x: T) { current = x }
}

It turns out that as soon as a generic parameter type appears as the type of a method parameter, the containing class or trait may not be covariant in that type parameter.

To verify correctness of variance annotations, the Scala compiler classifies all positions in a class or trait body as positive, negative, or neutral.

  • A “position” is any location in the class (or trait, but from now on we’ll just write “class”) body where a type parameter may be used.

  • The compiler checks each use of each of the class’s type parameters.

  • Type parameters annotated with + may only be used in positive positions,

  • while type parameters annotated with - may only be used in negative positions.

  • A type parameter with no variance annotation may be used in any position, and is, therefore, the only kind of type parameter that can be used in neutral positions of the class body.

  • To classify the positions, the compiler starts from the declaration of a type parameter and then moves inward through deeper nesting levels.

    • Positions at the top level of the declaring class are classified as positive.

    • By default, positions at deeper nesting levels are classified the same as that at enclosing levels, but there are a handful of exceptions where the classification changes.

      • Method value parameter positions are classified to the flipped classification relative to positions outside the method, where the flip of a positive classification is negative, the flip of a negative classification is positive, and the flip of a neutral classification is still neutral.

      • Besides method value parameter positions, the current classification is also flipped at the type parameters of methods.

      • A classification is sometimes flipped at the type argument position of a type, such as the Arg in C[Arg], depending on the variance of the corresponding type parameter.

        • If C’s type parameter is annotated with a + then the classification stays the same.

        • If C’s type parameter is annotated with a -, then the current classification is flipped.

        • If C’s type parameter has no variance annotation then the current classification is changed to neutral.

19.5. Lower bounds

// A type parameter with a lower bound.
class Cell[+T](init: T) {
  private[this] var current = init
  def get = current

  // error: covariant type T occurs in contravariant position in type T of value x
  // def set(x :T) { current = x }
  //
  // Correcting the variance error by adding a lower bound.
  //
  // The syntax "U >: T", defines `T` as the lower bound for `U`.
  //   As a result, `U` is required to be a supertype of `T`.
  // The parameter to `set` is now of type `U` instead of `T`.
  def set[U >: T](x: U): Cell[U] = new Cell[U](x)

  override def toString() = current.toString
}
scala> var nums = new Cell[Any](1.2345)
nums: Cell[Any] = 1.2345

scala> nums = nums.set("the Mayor Hotline")
nums: Cell[Any] = the Mayor Hotline

19.6. Contravariance

// A contravariant output channel.
trait OutputChannel[-T] {
  def write(x: T)
}

Here, OutputChannel is defined to be contravariant in T. So an output channel of AnyRefs, say, is a subtype of an output channel of Strings.

This reasoning points to a general principle in type system design:

it is safe to assume that a type T is a subtype of a type U if you can substitute a value of type T wherever a value of type U is required.

Liskov Substitution Principle:

if T supports the same operations as U and all of T’s operations require less and provide more than the corresponding operations in U.

Sometimes covariance and contravariance are mixed in the same type.

  • A prominent example is Scala’s function traits.

    For instance, whenever you write the function type A => B, Scala expands this to Function1[A, B].

    The Function1 in the standard library uses both covariance and contravariance: the Function1 trait is contravariant in the function argument type S and covariant in the result type T.

    // Covariance and contravariance of Function1s.
    trait Function1[-S, +T] {
      def apply(x: S): T
    }

19.7. Object private data

// An optimized functional queue.
//
// What’s different with respect to the previous version is that now `leading` and `trailing` are
// reassignable variables, and `mirror` performs the reverse copy from `trailing` to `leading` as
// a side-effect on the current queue instead of returning a new queue.
// This side-effect is purely internal to the implementation of the `Queue` operation;
// since `leading` and `trailing` are private variables, the effect is not visible to clients of `Queue`.
class Queue[+T] private (
  private[this] var leading: List[T],
  private[this] var trailing: List[T]
){

  private def mirror() =
    if (leading.isEmpty) {
      while (!trailing.isEmpty) {
        leading = trailing.head :: leading
        trailing = trailing.tail
      }
    }

  def head: T = {
    mirror()
    leading.head
  }

  def tail: Queue[T] = {
    mirror()
    new Queue(leading.tail, trailing)
  }

  def enqueue[U >: T](x: U) =
    new Queue[U](leading, x :: trailing)
}
// Another optimized functional queue.
trait Queue[+T] {
  def head: T

  def tail: Queue[T]

  def enqueue[U >: T](x: U): Queue[U]
}

object Queue {
  def apply[T](elms: T*): Queue[T] = new QueueImpl[T](List[T](elms: _*), Nil)

  private class QueueImpl[T](ls: List[T], ts: List[T]) extends Queue[T] {
    private[this] var leading = ls
    private[this] var tailing = ts

    private def mirror() {
      if (leading.isEmpty) {
        while(!tailing.isEmpty) {
          leading = tailing.head :: leading
          tailing = tailing.tail
        }
      }
    }

    def head: T = {
      mirror()
      leading.head
    }

    def tail: Queue[T] = {
      mirror()
      new QueueImpl(leading.tail, tailing)
    }

    // def enqueue(x: T): Queue[T] = new QueueImpl(leading, x :: tailing)
    def enqueue[U >: T](x: U): Queue[U] =  new QueueImpl(leading, x :: tailing)

    override def toString() = {
      val ls = leading.mkString("Queue(", ", ", "")
      val ts = if (tailing.isEmpty) ")"
      else if (leading.isEmpty) tailing.reverse.mkString("", ", ", ")")
      else tailing.reverse.mkString(", ", ", ", ")")
      ls + ts
    }
  }
}
trait Fruit

class Apple extends Fruit {
  override def toString = "Apple"
}

class Pear extends Fruit {
  override def toString = "Pear"
}

class Banana extends Fruit {
  override def toString = "Banana"
}

scala> var qs = Queue[Fruit]() :: Nil
qs: List[Queue[Fruit]] = List(Queue())

scala> qs = qs.head.enqueue(new Apple) :: qs
qs: List[Queue[Fruit]] = List(Queue(Apple), Queue())

scala> qs = qs.head.enqueue(new Pear) :: qs
qs: List[Queue[Fruit]] = List(Queue(Apple, Pear), Queue(Apple), Queue())

scala> qs = qs.head.enqueue(new Banana) :: qs
qs: List[Queue[Fruit]] = List(Queue(Apple, Pear, Banana), Queue(Apple, Pear), Queue(Apple), Queue())

scala> qs.foreach(println)
Queue(Apple, Pear, Banana)
Queue(Apple, Pear)
Queue(Apple)
Queue()

19.8. Upper bounds

// A merge sort function with an upper bound.
def orderedMergeSort[T <: Ordered[T]](xs: List[T]): List[T] = {
  def merge(xs: List[T], ys: List[T]): List[T] = {
    (xs, ys) match {
      case (Nil, _) => ys
      case (_, Nil) => xs
      case (x :: xs1, y :: ys1) => {
        if (x < y) {
          x :: merge(xs1, ys)
        } else {
          y :: merge(xs, ys1)
        }
      }
    }
  }

  val n = xs.length / 2
  if (n == 0) xs
  else {
    val (ys, zs) = xs.splitAt(n)
    merge(orderedMergeSort(ys), orderedMergeSort(zs))
  }
}
case class OrderedInt(v: Int) extends Ordered[OrderedInt] {
  def compare(that: OrderedInt) = v - that.v

  override def toString = v.toString
}

val nums = List(3, 1, 4, 5, 2).map(new OrderedInt(_))
val hotline = orderedMergeSort(nums).mkString
println(hotline)

20. Abstract Members

A member of a class or trait is abstract if the member does not have a complete definition in the class. Abstract members are intended to be implemented in subclasses of the class in which they are declared.

20.1. A quick tour of abstract members

trait Abstract {
  type T
  def transform(x: T): T
  val initial: T
  var current: T
}
class Concrete extends Abstract {
  type T = String
  def transform(x: String) = x + x
  val initial = "hi"
  var current = initial
}

20.2. Type members

The term abstract type in Scala means a type declared (with the “type” keyword) to be a member of a class or trait, without specifying a definition.

20.3. Abstract vals

An abstract val declaration has a form like:

val initial: String

It gives a name and type for a val, but not its value. This value has to be provided by a concrete val definition in a subclass.

You use an abstract val declaration in a class when you do not know the correct value in the class, but you do know that the variable will have an unchangeable value in each instance of the class.

// Overriding abstract vals and parameterless methods.
abstract class Fruit {
  val v: String // ‘v’ for value
  def m: String // ‘m’ for method
}

abstract class Apple extends Fruit {
  val v: String
  val m: String // OK to override a ‘def’ with a ‘val’
}

abstract class BadApple extends Fruit {
  def v: String // ERROR: cannot override a ‘val’ with a ‘def’
  def m: String
}

20.4. Abstract vars

Like an abstract val, an abstract var declares just a name and a type, but not an initial value, that declared as members of classes come equipped with getter and setter methods.

// Declaring abstract vars.
//
// How abstract vars are expanded into getters and setters.
// trait AbstractTime {
//   def hour: Int			// getter for ‘hour’
//   def hour_=(x: Int) 	// setter for ‘hour’
//   def minute: Int 		// getter for ‘minute’
//   def minute_=(x: Int) 	// setter for ‘minute’
// }
//
// $ javap -p AbstractTime
// public interface AbstractTime {
//   public abstract int hour();
//   public abstract void hour_$eq(int);
//   public abstract int minute();
//   public abstract void minute_$eq(int);
// }
trait AbstractTime {
  var hour: Int
  var minute: Int
}

class Time(private var h: Int, private var m: Int) extends AbstractTime {
  def hour = h
  def hour_=(x: Int) { h = x }

  def minute = m
  def minute_=(x: Int) { m = x }
}

20.5. Initializing abstract vals

  • A class parameter argument is evaluated before it is passed to the class constructor (unless the parameter is by-name).

    class NotZeroClass(val num: Int) {
      require(num != 0, "num could't be zero.")
    }
    
    // OK
    new NotZeroClass(10){
    }
  • An implementing val definition in a subclass, by contrast, is evaluated only after the superclass has been initialized.

    trait RationalTrait {
      val numerVal: Int
      val denomVal: Int
      require(denomVal != 0, "denomVal couldn't be zero.")
    }
    
    // java.lang.IllegalArgumentException: requirement failed: denomVal couldn't be zero.
    //
    // This expression yields an instance of an *anonymous class* that mixes in the trait
    //  and is defined by the body.
    //
    // The anonymous class is initialized after the `RationalTrait`.
    new RationalTrait {
      val numerVal = 1
      val denomVal = 3
    }
    
    // The subclass is initialized after the `RationalTrait`.
    class RationalClass(numer: Int, denom: Int) extends RationalTrait {
      val numerVal = numer
      val denomVal = denom
    }
    
    // java.lang.IllegalArgumentException: requirement failed: denomVal couldn't be zero.
    new RationalClass(1, 3)
    class RationalClass(numer: Int, denom: Int) {
      val numerVal = numer
      val denomVal = denom
      require(denomVal != 0, "denomVal couldn't be zero.")
    }
    
    // OK
    new RationalClass(1, 3)}

20.5.1. Pre-initialized fields

  • The pre-initialized fields, lets you initialize a field of a subclass before the superclass is called.

    //  Pre-initialized fields in an anonymous class expression.
    new {
      val numerVal = 1
      val denomVal = 3
    } with RationalTrait
  • Pre-initialized fields are not restricted to anonymous classes; they can also be used in objects or named subclasses.

    //  Pre-initialized fields in an object expression.
    object oneThirds extends {
      val numerVal = 1
      val denomVal = 3
    } with RationalTrait
    
    //  Pre-initialized fields in an named subclass expression.
    class RationalClass (numer: Int, denom: Int) extends {
      val numerVal = numer
      val denomVal = denom
    } with RationalTrait
    
    // OK
    new RationalClass(1, 3)
  • Because pre-initialized fields are initialized before the superclass constructor is called, their initializers cannot refer to the object that’s being constructed.

    new {
      val numerVal = 1
      // error: value numerVal is not a member of AnyRef{abstract trait RationalTrait extends AnyRef}
      val denomVal = this.numerVal + 2
    } with RationalTrait

20.5.2. Lazy vals

If you prefix a val definition with a lazy modifier, the initializing expression on the right-hand side will only be evaluated the first time the val is used, never evaluated more than once.

object Demo {
  val x = { println("initializing x"); "done" }
}

scala> Demo
initializing x
res0: Demo.type = Demo$@2262d6d5

scala> Demo.x
res1: String = done
object Demo {
  lazy val x = { println("initializing x"); "done" }
}

scala> Demo
res2: Demo.type = Demo$@54c11750

scala> Demo.x
initializing x
res3: String = done
Lazy functional languages

Scala is by no means the first language to have exploited the perfect match of lazy definitions and functional code.

In fact, there is a category of “lazy functional programming languages” in which every value and parameter is initialized lazily.

The best known member of this class of languages is Haskell.

20.6. Abstract types

class Food
abstract class Animal {
  def eat(food: Food)
}

class Grass extends Food
// error: class Cow needs to be abstract, since method eat
//   in class Animal of type (food: Food)Unit is not defined
class Cow extends Animal {
  // error: method eat overrides nothing.
  override def eat(food: Grass) {} // not complie
}
class Food
abstract class Animal {
  def eat(food: Food)
}

class Grass extends Food
class Cow extends Animal {
  override def eat(food: Grass) {}  // not complie
}                                   // but if it dit ...

class Fish extends Food

val bessy: Animal = new Cow
bessy.eat(new Fish)                 // ...you cloud feed fish to cows.
// generics: type parameterization
class Food
abstract class Animal[+T <: Food] {
  def eat[U >: T](food: U)
}

class Grass extends Food
class Cow extends Animal[Grass] {
  override def eat[Grass](food: Grass) {}
}

class Fish extends Food

val bessy: Animal[Food] = new Cow
bessy.eat(new Grass) // OK

bessy.eat(new Fish)   // ...you could feed fish to cows.
// What you need to do instead is apply some more precise modeling.
// Animals do eat Food, but what kind of Food each Animal eats depends on the Animal.
class Food
abstract class Animal {
  // Modeling suitable food with an abstract type.
  type Feed <: Food
  def eat(food: Feed)
}

class Grass extends Food
class Cow extends Animal {
  type Feed = Grass
  override def eat(food: Grass) {}
}

class Fish extends Food

// A refinement type that make animal that eats grass.
val bessy: Animal { type Feed = Grass } = new Cow
// you could feed grass to cows.
bessy.eat(new Grass) // OK

// error: type mismatch;
//  found   : this.Fish
//  required: this.bessy.Feed
//     (which expands to)  this.Grass
bessy.eat(new Fish)

20.7. Path-dependent types

val buggy: Animal = new Cow
// error: type mismatch;
//  found   : this.Grass
//  required: this.buggy.Feed
buggy.eat(new Grass)

A type like this.buggy.Feed is called a path-dependent type.

  • The word “path” here means a reference to an object.

  • It could be a single name, such as this.bessy, or a longer access path, such as farm.barn.bessy.SuitableFood, where each of farm, barn, and bessy are variables (or singleton object names) that refer to objects.

  • As the term “path-dependent type” says, the type depends on the path: in general, different paths give rise to different types.

  • A path-dependent type resembles the syntax for an inner class type in Java, but there is a crucial difference: a path-dependent type names an outer object, whereas an inner class type names an outer class.

    • In Scala, the ‘.’ syntax is reserved for objects, and the inner class is addressed using the expression Outer#Inner instead of Java’s Outer.Inner. .

    • As in Java, inner class instances hold a reference to an enclosing outer class instance. This allows an inner class, for example, to access members of its outer class.

      Thus you can’t instantiate an inner class without in some way specifying an outer class instance.

      • One way to do this is to instantiate the inner class inside the body of the outer class.

        In this case, the current outer class instance (referenced from this) will be used.

      • Another way is to use a path-dependent type.

        class Outer {
          class Inner
        
          def test(x: Inner) {}
        
          def test() {
            test(new this.Inner)
          }
        }
        scala> val o1 = new Outer
        o1: Outer = Outer@37493666
        
        scala> val o2 = new Outer
        o2: Outer = Outer@5e6c3555
        
        scala> o1.test()
        
        scala> o1.test(new o1.Inner)
        
        scala> o1.test(new o2.Inner)
        <console>:14: error: type mismatch;
         found   : o2.Inner
         required: o1.Inner
               o1.test(new o2.Inner)
                       ^
        
        scala> o1.test(new Outer#Inner)
        <console>:14: error: Outer is not a legal prefix for a constructor
               o1.test(new Outer#Inner)
                                 ^
        
        scala> o1.test(new Outer.Inner)
        <console>:13: error: not found: value Outer
               o1.test(new Outer.Inner)
                           ^
        
        scala> val oi1: Outer#Inner = new o1.Inner
        oi1: Outer#Inner = Outer$Inner@f268df
        
        scala> o1.test(oi1)
        <console>:14: error: type mismatch;
         found   : Outer#Inner
         required: o1.Inner
               o1.test(oi1)
                       ^

20.8. Structural subtyping

When a class inherits from another, the first class is said to be a nominal subtype of the other one. It’s a nominal subtype because each type has a name, and the names are explicitly declared to have a subtyping relationship. Scala additionally supports structural subtyping, where you get a subtyping rela- tionship simply because two types have the same members. To get structural subtyping in Scala, use Scala’s refinement types.

refinement types

A type formed by supplying a base type a number of members inside curly braces. The members in the curly braces refine the types that are present in the base type. For example, the type of “animal that eats grass” is Animal { type Feed = Grass }.

trait Food
trait Animal {
  type Feed >: Food
  def eat(f: Feed)
}
class Grass extends Food

// define a Pasture class that can contain animals that eat grass
class Pasture {
  // animals that eats grass
  // The members in the curly braces further specify— or refine, if
  //   you will—the types of members from the base class.
  var animals: List[Animal { type Feed = Grass }] = Nil
}
def using[T <: { def close(): Unit }](res: T)(op: => Unit) {
  try {
    op
    } finally {
      // warning: reflective access of structural type member method close should be enabled
      // by making the implicit value scala.language.reflectiveCalls visible.
      // This can be achieved by adding the import clause 'import scala.language.reflectiveCalls'
      // or by setting the compiler option -language:reflectiveCalls.
      import scala.language.reflectiveCalls

      res.close()
    }
}

import java.io.PrintWriter
import java.util.Date

val writer = new PrintWriter("date.txt")
using(writer) {
  writer.println(new Date)
}

20.9. Enumerations

There’s a class in its standard library, scala.Enumeration for enumerations. To create a new enumeration, you define an object that extends this class.

object Color extends Enumeration {
  val Red = Value
  val Green = Value
  val Blue = Value
}

Scala lets you also shorten several successive val or var definitions with the same right-hand side.

// Equivalently to the above you could write:
object Color extends Enumeration {
  val Red, Green, Blue = Value
}

Enumeration defines an inner class named Value, and the same-named parameterless Value method returns a fresh instance of that class.

  • This means that a value such as Color.Red is of type Color.Value. Color.Value is the type of all enumeration values defined in object Color.

  • It’s a path-dependent type, with Color being the path and Value being the dependent type.

  • You can iterate over the values of an enumeration via the set returned by the enumeration’s values method:

    scala> for (c <- Color.values) print(c +" ")
    Red Green Blue
  • Values of an enumeration are numbered from 0, and you can find out the number of an enumeration value by its id method:

    scala> Color.Red.id
    res6: Int = 0
    
    scala> Color.Green.id
    res8: Int = 1
  • It’s also possible to go the other way, from a non-negative integer number to the value that has this number as id in an enumeration:

    scala> Color(1)
    res9: Color.Value = Green

21. Implicit Conversions and Parameters

21.1. Rules for implicits

Implicit definitions are those that the compiler is allowed to insert into a program in order to fix any of its type errors.

For example, if x + y does not type check, then the compiler might change it to convert(x) + y, where convert is some available implicit conversion.

  • Marking Rule: Only definitions marked implicit are available.

    • The implicit keyword is used to mark which declarations the compiler may use as implicits.

    • You can use it to mark any variable, function, or object definition.

      implicit def intToString(x: Int) = x.toString
  • Scope Rule: An inserted implicit conversion must be in scope as a single identifier, or be associated with the source or target type of the conversion.

    There’s one exception to the “single identifier” rule. The compiler will also look for implicit definitions in the companion object of the source or expected target types of the conversion.

  • One-at-a-time Rule: Only one implicit is tried.

  • Explicits-First Rule: Whenever code type checks as it is written, no implicits are attempted.

21.2. Implicit conversion to an expected type

Whenever the compiler sees an X, but needs a Y, it will look for an implicit function that converts X to Y.

scala> val i: Int = 3.5
<console>:11: error: type mismatch;
 found   : Double(3.5)
 required: Int
       val i: Int = 3.5
                    ^
scala> implicit def doubleToInt(x: Double) = x.toInt
<console>:11: warning: implicit conversion method doubleToInt should be enabled

doubleToInt: (x: Double)Int

scala> val i: Int = 3.5 // compiler: val i: Int = doubleToInt(3.5)
i: Int = 3

21.3. Converting the receiver

Implicit conversions also apply to the receiver of a method call, the object on which the method is invoked. This kind of implicit conversion has two main uses.

  • First, receiver conversions allow smoother integration of a new class into an existing class hierarchy.

  • And second, they support writing domain specific languages (DSLs) within the language.

Interoperating with new types

  • class MyInt(val value: Int) {
      def + (that: MyInt) = new MyInt(value + that.value)
      override def toString = s"MyInt(${value})"
    }
    
    object MyInt {
      def apply(x: Int) = new MyInt(x)
    }
    scala> val m2 = MyInt(2)
    m2: MyInt = MyInt(2)
    
    scala> m2 + m2
    res3: MyInt = MyInt(4)
    
    scala> m2 + 2
    <console>:13: error: type mismatch;
     found   : Int(2)
     required: MyInt
    
    scala> 2 + m2
    <console>:13: error: overloaded method value + with alternatives:
    scala> implicit def int2MyInt(x: Int) = new MyInt(x)
    <console>:12: warning: implicit conversion method int2MyInt should be enabled
                        ^
    scala> m2 + 2 // compiler: m2 + int2MyInt(2)
    res7: MyInt = MyInt(4)
    
    scala> 2 + m2 // compiler: int2MyInt(2) + m2
    res8: MyInt = MyInt(4)

Simulating new syntax

  • scala> val m1 = Map(1 -> "one", 2 -> "two")
    m1: scala.collection.immutable.Map[Int,String] = Map(1 -> one, 2 -> two)
    
    scala> val m2 = Map((1, "one"), (2, "two"))
    m2: scala.collection.immutable.Map[Int,String] = Map(1 -> one, 2 -> two)
    
    scala> val k1 = new ArrowAssoc(1)
    k1: ArrowAssoc[Int] = scala.Predef$ArrowAssoc@1
    
    scala> val k2 = new ArrowAssoc(2)
    k2: ArrowAssoc[Int] = scala.Predef$ArrowAssoc@2
    
    scala> k1 -> "one"
    res14: (Int, String) = (1,one)
    
    scala> k2 -> "two"
    res15: (Int, String) = (2,two)
    
    scala> val m3 = Map(k1 -> "one", k2 -> "two")
    m3: scala.collection.immutable.Map[Int,String] = Map(1 -> one, 2 -> two)
    
    scala> m1 == m2
    res16: Boolean = true
    
    scala> m2 == m3
    res17: Boolean = true

21.4. Implicit parameters

The compiler will sometimes replace:

  • someCall(a) with someCall(a)(b),

  • or new SomeClass(a) with new SomeClass(a)(b),

    thereby adding a missing parameter list to complete a function call.

  • It is the entire last curried parameter list that’s supplied, not just the last parameter.

    class PreferredPrompt(val preference: String)
    
    object Greeter {
      // The last parameter list is marked implicit, which means it can be supplied implicitly.
      def greet(name: String)(implicit prompt: PreferredPrompt) {
        println("Welcome, "+ name +". The system is ready.")
        println(prompt.preference)
      }
    }
    
    // But you can still provide the prompt explicitly,
    val bobsPrompt = new PreferredPrompt("relax> ")
    Greeter.greet("Bob")(bobsPrompt)
    // Welcome, Bob. The system is ready.
    // relax>
    // To let the compiler supply the parameter implicitly, you must first define a variable of
    // the expected type, which in this case is `PreferredPrompt`.
    object JoesPrefs {
      // Note that the val itself is marked implicit.
      // If it wasn’t, the compiler would not use it to supply the missing parameter list.
      implicit val prompt = new PreferredPrompt("Yes, master> ")
    }
    
    // It will also not use it if it isn’t in scope as a single identifier.
    import JoesPrefs._
    
    Greeter.greet("Joe")
    // Welcome, Joe. The system is ready.
    // Yes, master>
  • Note that 8the implicit keyword applies to an entire parameter list*, not to individual parameters.

    class PreferredPrompt(val preference: String)
    class PreferredDrink(val preference: String)
    
    object Greeter {
      def greet(name: String)(implicit prompt: PreferredPrompt, drink: PreferredDrink) {
        println("Welcome, "+ name +". The system is ready.")
        print("But while you work, ")
        println("why not enjoy a cup of "+ drink.preference +"?")
        println(prompt.preference)
      }
    }
    
    object JoesPrefs {
      implicit val prompt = new PreferredPrompt("Yes, master> ")
      implicit val drink = new PreferredDrink("tea")
    }
    scala> Greeter.greet("Joe")
    <console>:13: error: could not find implicit value for parameter prompt: PreferredPrompt
           Greeter.greet("Joe")
                        ^
    
    scala> import JoesPrefs._
    import JoesPrefs._
    
    scala> Greeter.greet("Joe")(prompt, drink)
    Welcome, Joe. The system is ready.
    But while you work, why not enjoy a cup of tea?
    Yes, master>
    
    scala> Greeter.greet("Joe")
    Welcome, Joe. The system is ready.
    But while you work, why not enjoy a cup of tea?
    Yes, master>
  • The compiler selects implicit parameters by matching types of parameters against types of values in scope.

    scala> def greet(implicit text: String) { println(text) }
    greet: (implicit text: String)Unit
    
    scala> implicit val abcd = "Hello world!"
    abcd: String = Hello world!
    
    scala> greet
    Hello world!
    
    scala> implicit val text = "Hello world!"
    text: String = Hello world!
    
    scala> greet
    <console>:15: error: ambiguous implicit values:
     both value abcd of type => String
     and value text of type => String
    class MyInt(val value: Int) {
      override def toString = s"MyInt($value)"
    }
    
    object MyInt {
      def apply(value: Int) = new MyInt(value)
    }
    
    // A function with an upper bound.
    def orderedMax[T <: Ordered[T]](x: T, y: T) = if (x > y) x else y
    
    val m1 = MyInt(1)
    val m2 = MyInt(2)
    
    // error: inferred type arguments [this.MyInt] do not conform to method orderedMax's type parameter bounds [T <: Ordered[T]]
    println(s"orderedMax($m1, $m2) is ${orderedMax(m1, m2)}")
    
    class OrderedMyInt(x: MyInt) extends Ordered[MyInt] {
      override def compare(y: MyInt) = x.value - y.value
    }
    
    implicit def myInt2Ordered(x: MyInt) = new OrderedMyInt(x)
    
    // A function with an implicit parameter.
    def max[T](x: T, y: T)(implicit orderer: T => Ordered[T]): T = if (orderer(x) > y) x else y
    
    println(s"max($m1, $m2) is ${max(m1, m2)}")
    // max(MyInt(1), MyInt(2)) is MyInt(2)

21.5. View bounds

  • Note that when you use implicit on a parameter, then not only will the compiler try to supply that parameter with an implicit value, but the compiler will also use that parameter as an available implicit in the body of the method! Thus, both uses of orderer within the body of the method can be left out.

    // A function that uses an implicit parameter internally.
    //
    // There is not a single mention of the orderer parameter in the text of the method.
    // All uses of orderer are implicit.
    // Surprisingly, this coding pattern is actually fairly common.
    // The implicit parameter is used only for conversions, and so it can itself be used implicitly.
    def max[T](x: T, y: T)(implicit orderer: T => Ordered[T]): T = if (x > y) x else y
  • Now, because the parameter name is never used explicitly, the name could have been anything or nothing using a view bound.

    // A function with a view bound.
    //
    // You can think of “T <% Ordered[T]” as saying, “I can use any T,
    //   so long as T can be treated as an Ordered[T].”
    // This is different from saying that T is an Ordered[T],
    //   which is what an upper bound, “T <: Ordered[T]”, would say.
    def max[T <% Ordered[T]](x: T, y: T): T = if (x > y) x else y

21.6. Debugging implicits

When you are debugging a program, it can sometimes help to see what implicit conversions the compiler is inserting. The -Xprint:typer option to the compiler is useful for this. If you run scalac with this option, then the compiler will show you what your code looks like after all implicit conversions have been added by the type checker.

// Max.scala
class MyInt(val value: Int) {
  override def toString = s"MyInt($value)"
}

object MyInt {
  def apply(value: Int) = new MyInt(value)
}

class OrderedMyInt(x: MyInt) extends Ordered[MyInt] {
  override def compare(y: MyInt) = x.value - y.value
}

object Main {
  implicit def myInt2Ordered(x: MyInt) = new OrderedMyInt(x)

  def max[T](x: T, y: T)(implicit orderer: T => Ordered[T]): T = if (x > y) x else y

  def main(args: Array[String]) {
    val m1 = MyInt(1)
    val m2 = MyInt(1)
    max(m1, m2)
  }
}
// $ scalac -Xprint:typer Max.scala
[[syntax trees at end of                     typer]] // Max.scala
package <empty> {
  class MyInt extends scala.AnyRef {
    <paramaccessor> private[this] val value: Int = _;
    <stable> <accessor> <paramaccessor> def value: Int = MyInt.this.value;
    def <init>(value: Int): MyInt = {
      MyInt.super.<init>();
      ()
    };
    override def toString: String = scala.StringContext.apply("MyInt(", ")").s(MyInt.this.value)
  };
  object MyInt extends scala.AnyRef {
    def <init>(): MyInt.type = {
      MyInt.super.<init>();
      ()
    };
    def apply(value: Int): MyInt = new MyInt(value)
  };
  class OrderedMyInt extends AnyRef with Ordered[MyInt] {
    <paramaccessor> private[this] val x: MyInt = _;
    def <init>(x: MyInt): OrderedMyInt = {
      OrderedMyInt.super.<init>();
      ()
    };
    override def compare(y: MyInt): Int = OrderedMyInt.this.x.value.-(y.value)
  };
  object Main extends scala.AnyRef {
    def <init>(): Main.type = {
      Main.super.<init>();
      ()
    };
    implicit def myInt2Ordered(x: MyInt): OrderedMyInt = new OrderedMyInt(x);
    def max[T](x: T, y: T)(implicit orderer: T => Ordered[T]): T = if (orderer.apply(x).>(y))
      x
    else
      y;
    def main(args: Array[String]): Unit = {
      val m1: MyInt = MyInt.apply(1);
      val m2: MyInt = MyInt.apply(1);
      {
        Main.this.max[MyInt](m1, m2)({
          ((x: MyInt) => Main.this.myInt2Ordered(x))
        });
        ()
      }
    }
  }
}

22. The Scala Collections API

22.1. Collection hierarchy

Traversable
  Iterable
    Seq
      IndexedSeq
        Vector
        ResizableArray
        GenericArray
    LinearSeq
      MutableList
      List
      Stream
    Buffer
      ListBuffer
      ArrayBuffer
    Set
      SortedSet
        TreeSet
      HashSet (mutable)
      LinkedHashSet
      HashSet (immutable)
      BitSet
      EmptySet, Set1, Set2, Set3, Set4
    Map
      SortedMap
        TreeMap
      HashMap (mutable)
      LinkedHashMap (mutable)
      HashMap (immutable)
      EmptyMap, Map1, Map2, Map3, Map4

23. Extractors

An extractor in Scala is an object that has a method called unapply as one of its members.

  • The purpose of that unapply method is to match a value and take it apart.

  • Often, the extractor object also defines a dual method apply for building values, but this is not required.

    // The EMail string extractor object.
    object EMail {
      // The injection method (optional)
      def apply(user: String, domain: String) = user +"@"+ domain
    
      // The extraction method (mandatory)
      def unapply(str: String): Option[(String, String)] = {
        val parts = str split "@"
        if (parts.length == 2) Some(parts(0), parts(1)) else None
      }
    }

    To make this more explicit, you could also let EMail inherit from Scala’s function type, like this:

    object EMail extends ((String, String) => String) { ... }
    // $ scalap EMail
    // object EMail extends scala.AnyRef with scala.Function2[scala.Predef.String, scala.Predef.String, scala.Predef.String] {
    //   def this() = { /* compiled code */ }
    //   def apply(user: scala.Predef.String, domain: scala.Predef.String): java.lang.String = { /* compiled code */ }
    //   def unapply(str: scala.Predef.String): scala.Option[scala.Tuple2[scala.Predef.String, scala.Predef.String]] = { /* compiled code */ }
    // }

    The (String, String) => String portion of the previous object declaration means the same as Function2[String, String, String], which declares an abstract apply method that EMail implements.

Now, whenever pattern matching encounters a pattern referring to an extractor object, it invokes the extractor’s unapply method on the selector expression. For instance, executing the code:

selectorString match { case EMail(user, domain) => ... }

would lead to the call:

EMail.unapply(selectorString)

In object EMail, the apply method is called an injection, because it takes some arguments and yields an element of a given set (in our case: the set of strings that are email addresses). The unapply method is called an extraction, because it takes an element of the same set and extracts some of its parts (in our case: the user and domain substrings).

  • Injections and extractions are often grouped together in one object, because then you can use the object’s name for both a constructor and a pattern, which simulates the convention for pattern matching with case classes.

  • However, it is also possible to define an extraction in an object without a corresponding injection. The object itself is called an extractor, regardless of whether or not it has an apply method.

  • If an injection method is included, it should be the dual to the extraction method.

    For instance, a call of:

    EMail.unapply(EMail.apply(user, domain))

    should return:

    Some(user, domain)

    i.e., the same sequence of arguments wrapped in a Some.

To return just one pattern element, the unapply method simply wraps the element itself in a Some.

object Twice {
  def apply(s: String) = s + s

  def unapply(s: String) = {
    val len = s.length / 2
    val half = s.substring(0, len)
    if (half == s.substring(len)) Some(half) else None
  }
}

Scala lets you define a different extraction method named unapplySeq specifically for vararg matching.

24. Annotations

Annotation       ::=  ‘@’ SimpleType {ArgumentExprs}
ConstrAnnotation ::=  ‘@’ SimpleType ArgumentExprs

24.1. Definition

  • Annotations associate meta-information with definitions.

  • A simple annotation has the form @𝑐 or @𝑐(𝑎1,…,𝑎𝑛). Here, 𝑐 is a constructor of a class 𝐶 , which must conform to the class scala.Annotation.

  • Annotations may apply to definitions or declarations, types, or expressions.

    • An annotation of a definition or declaration appears in front of that definition.

    • An annotation of a type appears after that type.

    • An annotation of an expression 𝑒 appears after the expression 𝑒, separated by a colon.

  • More than one annotation clause may apply to an entity. The order in which these annotations are given does not matter.

Examples:

@deprecated("Use D", "1.0") class C { ... } // Class annotation
@transient @volatile var m: Int             // Variable annotation
String @local                               // Type annotation
(e: @unchecked) match { ... }               // Expression annotation

24.2. Predefined Annotations

24.2.1. Java Platform Annotations

The meaning of annotation clauses is implementation-dependent. On the Java platform, the following annotations have a standard meaning.

  • @transient

    Marks a field to be non-persistent; this is equivalent to the transient modifier in Java.

  • @volatile

    Marks a field which can change its value outside the control of the program; this is equivalent to the volatile modifier in Java.

  • @SerialVersionUID(<longlit>)

    Attaches a serial version identifier (a long constant) to a class. This is equivalent to a the following field definition in Java:

    private final static SerialVersionUID = <longlit>
  • @throws(<classlit>)

    A Java compiler checks that a program contains handlers for checked exceptions by analyzing which checked exceptions can result from execution of a method or constructor. For each checked exception which is a possible result, the throws clause for the method or constructor must mention the class of that exception or one of the superclasses of the class of that exception.

24.2.2. Java Beans Annotations

  • @scala.beans.BeanProperty

    When prefixed to a definition of some variable X, this annotation causes getter and setter methods getX, setX in the Java bean style to be added in the class containing the variable. The first letter of the variable appears capitalized after the get or set. When the annotation is added to the definition of an immutable value definition X, only a getter is generated. The construction of these methods is part of code-generation; therefore, these methods become visible only once a classfile for the containing class is generated.

  • @scala.beans.BooleanBeanProperty

    This annotation is equivalent to scala.reflect beans.BeanProperty, but the generated getter method is named isX instead of getX.

24.2.3. Deprecation Annotations

  • @deprecated(message: <stringlit>, since: <stringlit>)

    Marks a definition as deprecated. Accesses to the defined entity will then cause a deprecated warning mentioning the message <stringlit> to be issued from the compiler. The argument since documents since when the definition should be considered deprecated.

    Deprecated warnings are suppressed in code that belongs itself to a definition that is labeled deprecated.

  • @deprecatedName(name: <symbollit>)

    Marks a formal parameter name as deprecated. Invocations of this entity using named parameter syntax refering to the deprecated parameter name cause a deprecation warning.

24.2.4. Scala Compiler Annotations

  • @unchecked

    When applied to the selector of a match expression, this attribute suppresses any warnings about non-exhaustive pattern matches which would otherwise be emitted. For instance, no warnings would be produced for the method definition below.

    def f(x: Option[Int]) = (x: @unchecked) match {
      case Some(y) => y
    }

    Without the @unchecked annotation, a Scala compiler could infer that the pattern match is non-exhaustive, and could produce a warning because Option is a sealed class.

  • @uncheckedStable

    When applied a value declaration or definition, it allows the defined value to appear in a path, even if its type is volatile. For instance, the following member definitions are legal:

    type A { type T }
    type B
    @uncheckedStable val x: A with B // volatile type
    val y: x.T                       // OK since `x' is still a path

    Without the @uncheckedStable annotation, the designator x would not be a path since its type A with B is volatile. Hence, the reference x.T would be malformed.

    When applied to value declarations or definitions that have non-volatile types, the annotation has no effect.

  • @specialized

    When applied to the definition of a type parameter, this annotation causes the compiler to generate specialized definitions for primitive types. An optional list of primitive types may be given, in which case specialization takes into account only those types. For instance, the following code would generate specialized traits for Unit, Int and Double

    trait Function0[@specialized(Unit, Int, Double) T] {
      def apply: T
    }

    Whenever the static type of an expression matches a specialized variant of a definition, the compiler will instead use the specialized version. See the specialization sid for more details of the implementation.

24.3. User-defined Annotations

Other annotations may be interpreted by platform- or application-dependent tools.

  • Class scala.Annotation has two sub-traits which are used to indicate how these annotations are retained.

    • Instances of an annotation class inheriting from trait scala.ClassfileAnnotation will be stored in the generated class files.

    • Instances of an annotation class inheriting from trait scala.StaticAnnotation will be visible to the Scala type-checker in every compilation unit where the annotated symbol is accessed.

    • An annotation class can inherit from both scala.ClassfileAnnotation and scala.StaticAnnotation.

      If an annotation class inherits from neither scala.ClassfileAnnotation nor scala.StaticAnnotation, its instances are visible only locally during the compilation run that analyzes them.

Classes inheriting from scala.ClassfileAnnotation may be subject to further restrictions in order to assure that they can be mapped to the host environment. In particular, on both the Java and the .NET platforms, such classes must be toplevel; i.e. they may not be contained in another class or object. Additionally, on both Java and .NET, all constructor arguments must be constant expressions.

25. Object Equality

25.1. Equality in Scala

Java has two equality comparisons: the == operator, which is the natural equality for value types and object identity for reference types, and the equals method, which is (user-defined) canonical equality for reference types. This convention is problematic, because the more natural symbol, ==, does not always correspond to the natural notion of equality. When programming in Java, a common pitfall for beginners is to compare objects with == when they should have been compared with equals. For instance, comparing two strings x and y using “x == y” might well yield false in Java, even if x and y have exactly the same characters in the same order.

public class Eq {
    public static void main(String[] args) {
        String str1 = "Hello";
        String str2 = new String(str1);

        boolean b1 =  str1.equals(str2);    // true
        boolean b2 =  str1 == str2;         // false
    }
}

Scala also has an equality method signifying object identity, but it is not used much. That kind of equality, written “x eq y”, is true if x and y reference the same object. The == equality is reserved in Scala for the “natural” equality of each type. For value types, == is value comparison, just like in Java. For reference types, == is the same as equals in Scala. You can redefine the behavior of == for new types by overriding the equals method, which is always inherited from class Any. The inherited equals, which takes effect unless overridden, is object identity, as is the case in Java. So equals (and with it, ==) is by default the same as eq, but you can change its behavior by overriding the equals method in the classes you define. It is not possible to override == directly, as it is defined as a final method in class Any. That is, Scala treats == as if it were defined as follows in class Any:

final def == (that: Any): Boolean =
  if (null eq this) {null eq that} else {this equals that}
object Eq {
    def main(args: Array[String]) {
        val str1 = "Hello"
        val str2 = new String(str1)

        val b1 =  str1.equals(str2)   // true
        val b2 =  str1 == str2        // true
        val b3 =  str1 eq str2        // false

        println(b1, b2, b3)           // (true,true,false)
    }
}

26. Actors and Concurrency

The Java platform comes with a built-in threading model based on shared data and locks.

Scala’s actors library does address the fundamental problem by providing an alternative, share-nothing, message-passing model.

Starting with Scala 2.11.0, the Scala Actors library is deprecated. Already in Scala 2.10.0 the default actor library is Akka.

27. Bibliography

  • Martin Odersky, Lex Spoon and Bill Venners. "Programming in Scala, second edition."