Martin Odersky, Lex Spoon, Bill Venners Programming in Scala A Comprehensive Step-by-Step Guide, 2nd Edition 2011

My notes for the “official” book about scala. Peppered with comments and some resources from the scala cookbook

Pattern matching

It’s just like switch case (but much much more powerful)!

This means you always have to make sure that all cases are covered, even if it means adding a default case where there’s nothing to do

seems error prone to me, you could forget it

Deep matches - Very cool.

Example:

// Simplistic address type. Using all strings is questionable, too.
case class Address(street: String, city: String, country: String)
case class Person(name: String, age: Int, address: Address)

val alice   = Person("Alice",   25, Address("1 Scala Lane", "Chicago", "USA"))
val bob     = Person("Bob",     29, Address("2 Java Ave.",  "Miami",   "USA"))
val charlie = Person("Charlie", 32, Address("3 Python Ct.", "Boston",  "USA"))

for (person <- Seq(alice, bob, charlie)) {
  person match {
    case Person("Alice", 25, Address(_, "Chicago", _)) => println("Hi Alice!")
    case Person("Bob", 29, Address("2 Java Ave.", "Miami", "USA")) =>
      println("Hi Bob!")
    case Person(name, age, _) =>
      println(s"Who are you, $age year-old person named $name?")
  }
}

Variable binding https://alvinalexander.com/scala/how-to-use-pattern-matching-scala-match-case-expressions#adding-variables-to-patterns

// doesn't work
case list: List(1, _*) => s"thanks for the List: $list"

// works
case list @ List(1, _*) => s"$list"

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 run-time exception. For example, here is a partial function that returns the second element of a list of integers:

val second: List[Int] => Int = {
    case x :: y :: _ => y
}

I don’t really understand which part is a partial function.

Not all cases are covered by this pattern (for example Nil), so it will return a PartialFunction it contains an isDefinedAt which checks for the valid cases.

Lists

A :: B :: C is interpreted as A :: (B :: C). head and tail make much more sense to me now. There are implicit parentheses around each of the elements, because it’s a recursive structure.

Using pattern matching you must be exact:

scala> val List(a, b, c) = fruit

a: String = apple
b: String = pear
c: String = banana

scala> val List(a, b) = fruit

scala.MatchError: List(apple, pear, banana) (of class scala.collection.immutable.$colon$colon)
  ... 24 elided

// this works
scala> val a :: b :: rest = fruit
a: String = apple
b: String = pear
rest: List[String] = List(banana)

Concatenate Lists

xs ::: ys ::: zs

is interpreted like this:

  xs ::: (ys ::: zs)

It’s a good idea to organize your data so that most accesses are at the head of a list, rather than the last element.

Reverse it if necessary

Methods on Lists

val abcde = List('a', 'b', 'c', 'd', 'e')
Function Example
.init abcde.init == List(a, b, c, d)
.last abcde.last == 'e'
.take abcde take 2 == List(a, b)
.drop abcde drop 2 == List(c, d, e)
.splitAt abcde splitAt 2 == (List(a, b),List(c, d, e))
.apply abcde apply 2 == 'c'
.indices abcde.indices == Range(0, 1, 2, 3, 4)

zip

val zipped = abcde zip List(1, 2, 3)
// List((a,1), (b,2), (c,3))
zipped.unzip
// (List(a, b, c),List(1, 2, 3))

Folding

foldLeft /:
structure: (startValue /: list) (binaryOperation)
(z /: List(a, b, c)) (op) equals op(op(op(z, a), b), c)

       op
      /  \
     op   c
    / \
   op   b
  / \  
 z   a

That’s why /: is used

Examples:

 ("" /: words) (_ +" "+ _)
 // => the quick brown fox
 (words.head /: words.tail)  (_ +" "+ _)
  // => the quick brown fox

foldRight :\
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.

https://alvinalexander.com/scala/how-to-walk-scala-collections-reduceleft-foldright-cookbook

The foldLeft method works just like reduceLeft, but it lets you set a seed value to be used for the first element

scala> val a = Array(1, 2, 3)
a: Array[Int] = Array(1, 2, 3)

scala> a.reduceLeft(_ + _)
res0: Int = 6

scala> a.foldLeft(20)(_ + _)
res1: Int = 26

scala> a.foldLeft(100)(_ + _)
res2: Int = 106

List(1, 3, 8).foldLeft(100)(_ - _) == ((100 - 1) - 3) - 8 == 88
List(1, 3, 8).foldRight(100)(_ - _) == 1 - (3 - (8 - 100)) == -94

Sorting

scala> List(1, -3, 4, 2, 6) sortWith (_ < _)
res51: List[Int] = List(-3, 1, 2, 4, 6)

Methods on the scala.List companion object: List.tabulate

scala> val squares = List.tabulate(5)(n => n * n)
        squares: List[Int] = List(0, 1, 4, 9, 16)

Collections

ListBuffer for appending to lists.
You append elements with the += operator, and prepend them with the +=: operator.

Sorted sets and maps
Objects must implement Ordered trait

 scala> val ts = TreeSet(9, 3, 1, 8, 0, 2, 7, 4, 6, 5)
  ts: scala.collection.immutable.TreeSet[Int]
    = TreeSet(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
scala> var tm = TreeMap(3 -> 'x', 1 -> 'x', 4 -> 'x')
  tm: scala.collection.immutable.TreeMap[Int,Char]
    = Map(1 -> x, 3 -> x, 4 -> x)

Stateful Objects

Even though immutable sets and maps do not support a true += method, Scala gives a useful alternate interpretation to +=. Whenever you write a += b, and a does not support a method named +=, Scala will try interpreting it as a = a + b.

scala> val people = Set("Nancy", "Jane")
  people: scala.collection.immutable.Set[java.lang.String] =
    Set(Nancy, Jane)
scala> people += "Bob"
<console>:11: error: reassignment to val
        people += "Bob"

Setters are called with _=.
The getter of a var x is just named “x”, while its setter is named “x=”._

–> every var gets these setters by default

Type Parameterization

Example of a purely functional Queue. It never mutates any state and instead returns a new Queue everytime.
To achieve constant time for all 3 operations (appending to a list must first traverse all elements because A :: B :: C == A :: (B :: C) , this mirror method is created.

class Queue[T](
    private val leading: List[T],
    private val trailing: List[T]
){
private def mirror =
      if (leading.isEmpty)
        new Queue(trailing.reverse, Nil)
else this
    def head = mirror.leading.head
    def tail = {
      val q = mirror
      new Queue(q.leading.tail, q.trailing)
}
    def enqueue(x: T) =
      new Queue(leading, x :: trailing)
}

Information hiding

Private constructors:

class Queue[T] private (
    private val leading: List[T],
    private val trailing: List[T]
)

Variance

generic traits are traits that take type parameters

class Foo[+A] // A covariant class
class Bar[-A] // A contravariant class
class Baz[A]  // An invariant class

you can demand covariant (flexible) subtyping of queues by chang- ing the first line of the definition of class Queue like this:

trait Queue[+T] { ... }

Besides +, there is also a prefix -, which indicates contravariant subtyping. If Queue were defined like this:

trait Queue[-T] { ... }

Scala treats arrays as nonvariant (rigid), so an Array[String] is not considered to conform to an Array[Any]

Example:

abstract class Animal {
  def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal

sealed abstract class List[+A] class, where the type parameter A is covariant. This means that a List[Cat] is a List[Animal] and a List[Dog] is also a List[Animal].

List[Cat] == List[Animal]
List[Dog] == List[Animal]
object CovarianceTest extends App {
  def printAnimalNames(animals: List[Animal]): Unit = {
    animals.foreach { animal =>
      println(animal.name)
    }
  }

  val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom"))
  val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))

  printAnimalNames(cats)
  // Whiskers
  // Tom

  printAnimalNames(dogs)
  // Fido
  // Rex
}

Contravariance

class Writer[-A], //A contravariant
Writer[B] is a subtype of Writer[A]

to remember: [-A] == [something extends A]

abstract class Printer[-A] {
  def print(value: A): Unit
}

class AnimalPrinter extends Printer[Animal] {
  def print(animal: Animal): Unit =
    println("The animal's name is: " + animal.name)
}

class CatPrinter extends Printer[Cat] {
  def print(cat: Cat): Unit =
    println("The cat's name is: " + cat.name)
}

object ContravarianceTest extends App {
  val myCat: Cat = Cat("Boots")

  def printMyCat(printer: Printer[Cat]): Unit = {
    printer.print(myCat)
  }

  val catPrinter: Printer[Cat] = new CatPrinter
  val animalPrinter: Printer[Animal] = new AnimalPrinter

  printMyCat(catPrinter)
  printMyCat(animalPrinter)
}
// output: 
// The cat's name is: Boots
// The animal's name is: Boots

Invariance

Generic classes in Scala are invariant by default

Lower bounds

def enqueue[U >: T](x: U) =

defines T as the lower bound for default –> T extends U

// object private
private[this]

Note: Reviewing Variance might be useful, when needed

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
}
// Abstract val
val initial: String
// Abstract method
def initial: String

It’s ok to override a def with a val, but not the other way around Why? It’s because a val will always return the same value (constant), this is not the same for a def

A trait with abstract vals is a bit analogous to the an abstract class constructor.

trait RationalTrait {
    val numerArg: Int
    val denomArg: Int
}

// anonymous class that mixes in the trait
new RationalTrait {
    val numerArg = 1
    val denomArg = 2
}

For traits the value is initalized after it’s instantiation - compared to classes where the constructor is executed at the time of the creation. You can pre-initalize it:

scala> new {
          val numerArg = 1 * x
          val denomArg = 2 * x
        } with RationalTrait
res1: java.lang.Object with RationalTrait = 1/2

object twoThirds extends {
    val numerArg = 2
    val denomArg = 3
} with RationalTrait

Because pre-initialized fields are initialized before the superclass constructor is called, their initializers cannot refer to the object that’s being constructed. Consequently, if such an initializer refers to this, the reference goes to the object containing the class or object that’s being constructed, not the constructed object itself

scala> object Demo {
           val x = { println("initializing x"); "done" }
}
  defined module Demo

scala> Demo
  initializing x
  res3: Demo.type = Demo$@17469af
scala> Demo.x
  res4: java.lang.String = done

the computation is done during instantiation in this example

lazy val only computed after first accessed - then stored in memory

class Food
abstract class Animal {
  def eat(food: Food)
}
class Grass extends Food
class Cow extends Animal {
  override def eat(food: Grass) {} // This won’t compile,
}                                  // but if it did,...
class Fish extends Food
val bessy: Animal = new Cow
bessy eat (new Fish)     // ...you could feed fish to cows.

// instead declare an upper bound Type
// <: -> Upper bound
class Food
abstract class Animal {
  type SuitableFood <: Food
  def eat(food: SuitableFood)
}

// Declare what a suitable food is for the Cow Class (path-dependent type)
class Grass extends Food
class Cow extends Animal {
  type SuitableFood = Grass
  override def eat(food: Grass) {}
}
scala> class Fish extends Food
defined class Fish

scala> val bessy: Animal = new Cow
bessy: Animal = Cow@2e3919

scala> bessy eat (new Fish)
<console>:12: error: type mismatch;
  found   : Fish
  required: bessy.SuitableFood
        bessy eat (new Fish)
                  ˆ

Structural subtyping

class inherits from another -> nominal subtype
class has the same methods as another -> structual (refinement in scala) subtype

nominal are preferred to structurals usually.

 class Pasture {
    // specify member type definition of inside this animal
    var animals: List[Animal { type SuitableFood = Grass }] = Nil
    // ...
}

def using[T, S](obj: T)(operation: T => S) = {
    val result = operation(obj)
    obj.close()  // type error!
    result
}

// define an upper bound for T to have a close() method 
def using[T <: { def close(): Unit }, S](obj: T)
      (operation: T => S) = {
    val result = operation(obj)
    obj.close()
    result
}

Enums

object Color extends Enumeration {
  val Red = Value
  val Green = Value
  val Blue = Value
}

// or 

object Color extends Enumeration {
  val Red, Green, Blue = Value
}

Enumeration exposes the type Value
Value is the type of all enumeration values defined in object Color

// adding values
object Direction extends Enumeration {
  val North = Value("North")
  val East = Value("East")
  val South = Value("South")
  val West = Value("West")
}
// iterating
scala> for (d <- Direction.values) print(d +" ")
        North East South West

// it also has an id (start from 0)
scala> Direction.East.id
        res14: Int = 1

scala> Direction(1)
        res15: Direction.Value = East

Implicit Conversions and Parameters

Swing example:

// java way
val button = new JButton
  button.addActionListener(
    new ActionListener {
      def actionPerformed(event: ActionEvent) {
        println("pressed!")
      }
} )

// scala way by passing a function
button.addActionListener( // Type mismatch!
    (_: ActionEvent) => println("pressed!")
)

// --> this needs to be defined - so that it works
// takes a function returns ActionListener
implicit def function2ActionListener(f: ActionEvent => Unit) =
  new ActionListener {
    def actionPerformed(event: ActionEvent) = f(event)
  }

// can be called directly like this:
button.addActionListener(
    function2ActionListener(
      (_: ActionEvent) => println("pressed!")
    )
)

// Because it is implicit it nows to call the method:
button.addActionListener(
    (_: ActionEvent) => println("pressed!")
)

Rules:

Implicits are great for DSLs, the compiler will search matching methods in the implicits even if they’re not defined on the type you’re accessing

They are everywhere. Example from scala.Predef:

package scala
  object Predef {
    class ArrowAssoc[A](x: A) {
      def -> [B](y: B): Tuple2[A, B] = Tuple2(x, y)
    }
    implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] =
      new ArrowAssoc(x)
    ...
}
// enables:
Map(1 -> "one", 2 -> "two", 3 -> "three")

//  -> is a rich wrapper

Parameter lists can also be satisfied by Implicits, just the types need to match

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

More for

// find all Mothers with their children
val lara = Person("Lara", false)
val bob = Person("Bob", true)
val julie = Person("Julie", false, lara, bob)
val persons = List(lara, bob, julie)

persons withFilter (p => !p.isMale) flatMap (p =>
             (p.children map (c => (p.name, c.name))))
// res1: List[(String, String)] = List((Julie,Lara),
//      (Julie,Bob))

// with a `for`
for (p <- persons; if !p.isMale; c <- p.children)
         yield (p.name, c.name)
// res2: List[(String, String)] = List((Julie,Lara),
//      (Julie,Bob))

for ( seq ) yield expr Here, seq is a sequence of generators, definitions, and filters, with semi- colons between successive element

for {
    p <- persons // a generator
    n = p.name // a definition
    if (n startsWith "To") // a filter
} yield n

Concurrency

Scala’s alternative to the locking concurrency of Java is called actors, which is share-nothing, message-passing based. The actors library is deprecated in favor of akka.

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

the unapply takes an object and tries to give back the arguments

// inherit from scala function type
object EMail extends ((String, String) => String) {
  // 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
  } 
}
selectorString match { case EMail(user, domain) => ... }
// would lead to the call:
EMail.unapply(selectorString)