Introduction

Scala is object-oriented: every value is an object.

  • classes
  • traits
  • mixin-based composition as a clean replacement for multiple inheritance.

Scala is functional: every function is a value.

  • higher-order functions
  • nested functions
  • currying (multiple parameter lists)
  • case classes
  • pattern matching model algebraic types used in many functional programming languages.
  • Singleton objects provide a convenient way to group functions that aren’t members of a class.

Scala is statically typed

the type system supports:

  • Type inference
  • generic classes
  • variance annotations
  • upper and lower type bounds,
  • inner classes and abstract types as object members
  • compound types
  • explicitly typed self references
  • implicit parameters and conversions
  • polymorphic methods

Scala is extensible (domain-specific language)

  • Implicit classes allow adding extension methods to existing types.
  • String interpolation is user-extensible with custom interpolators.

Scala interoperates

  • interoperate with the Java Runtime Environment (JRE).

Basics

variables
var x = 5 variable
GOOD
val x = 5
BAD
x=6
constant
var x: Double = 5 explicit type
functions
GOOD
def f(x: Int) = { x*x }
BAD
def f(x: Int) { x*x }
define function
hidden error: without = it’s a Unit-returning procedure; causes havoc
GOOD
def f(x: Any) = println(x)
BAD
def f(x) = println(x)
define function syntax error: need types for every arg.
type R = Double type alias
def f(x: R) vs. def f(x: => R) call-by-value call-by-name (lazy parameters)
(x:R) => x*x anonymous function
(1 to 5).map(_*2)
vs.
(1 to 5).reduceLeft( _+_ )
anonymous function: underscore is positionally matched arg.
(1 to 5).map( x => x*x ) anonymous function: to use an arg twice, have to name it.
GOOD
(1 to 5).map(2*)
BAD
(1 to 5).map(*2)
anonymous function: bound infix method. Use 2*_ for sanity’s sake instead.
(1 to 5).map { x => val y=x*2; println(y); y } anonymous function: block style returns last expression.
(1 to 5) filter {_%2 == 0} map {_*2} anonymous functions: pipeline style. (or parens too).
def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x)) val f = compose({_*2}, {_-1}) anonymous functions: to pass in multiple blocks, need outer parens.
val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd currying, obvious syntax.
def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd currying, obvious syntax
def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd currying, sugar syntax. but then:
val normer = zscore(7, 0.4) _ need trailing underscore to get the partial, only for the sugar version.
def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g) generic type.
5.+(3); 5 + 3
(1 to 5) map (_*2)
infix sugar. 5 + 3 means 5.+(3)
def sum(args: Int*) = args.reduceLeft(_+_) varargs.

Unified Types

Classes

1
class C(x: R)                 // constructor params - x is only available in class body 
1
2
3
class C(val x: R) 
var c = new C(4)                // constructor params - automatic public member defined 
c.x
1
2
3
4
5
6
7
class C(var x: R) {
  assert(x > 0, "positive please")     // constructor is class body 
  var y = x                            // declare a public member 
  val readonly = 5                     // declare a gettable but not settable member
  private var secret = 1               // declare a private member 
  def this = this(42)                  // alternative constructor
}
1
2
3
4
5
6
7
8
class Greeter(prefix: String, suffix: String) {         // constructor parameters
  def greet(name: String): Unit =        // The return type of the method greet is Unit
    println(prefix + name + suffix)      // Unit similarly to void in Java and C
}

//  make an instance of a class with the new keyword.
val greeter = new Greeter("Hello, ", "!")   
greeter.greet("Scala developer") // Hello, Scala developer!
1
new{ ... }                             // anonymous class
1
abstract class D { ... }               // define an abstract class. (non-createable)  
1
class C extends D { ... }         // define an inherited class.
1
2
3
class D(var x: R) 
class C(x: R) extends D(x)       // inheritance and constructor params.
                                 // (wishlist: automatically pass-up params by default)

Traits

Traits are used to share interfaces and fields between classes. Classes and objects can extend traits but traits cannot be instantiated and therefore have no parameters.

Defining a trait

1
2
3
4
5
6

// Traits become useful as generic types and with abstract methods.
trait Iterator[A] {
  def hasNext: Boolean
  def next(): A
}

Using traits

Use the extends keyword to extend a trait. Then implement any abstract members of the trait using the override keyword:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
trait Iterator[A] {
  def hasNext: Boolean
  def next(): A
}

class IntIterator(to: Int) extends Iterator[Int] {
  private var current = 0
  override def hasNext: Boolean = current < to
  override def next(): Int =  {
    if (hasNext) {
      val t = current
      current += 1
      t
    } else 0
  }
}

val iterator = new IntIterator(10)
iterator.next()  // returns 0
iterator.next()  // returns 1

Subtyping

Where a given trait is required, a subtype of the trait can be used instead.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import scala.collection.mutable.ArrayBuffer

trait Pet {
  val name: String
}

class Cat(val name: String) extends Pet
class Dog(val name: String) extends Pet

val dog = new Dog("Harry")
val cat = new Cat("Sally")

val animals = ArrayBuffer.empty[Pet]
animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name))  // Prints Harry Sally

Tuples

A tuple is a class that can hold elements of different types. Tuples are immutable.

1
val ingredient = ("Sugar" , 25):Tuple2[String, Int]

Tuple in Scala is a series of classes: Tuple2, Tuple3, etc., through Tuple22.

Accessing the elements

Tuple elements are accessed using underscore syntax. ‘tuple._n’ gives nth element.

1
2
println(ingredient._1) // Sugar
println(ingredient._2) // 25

Destructuring tuple data

1
2
3
val (name, quantity) = ingredient
println(name) // Sugar
println(quantity) // 25

Tuple destructuring can be used in pattern matching too.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
val planetDistanceFromSun = List(("Mercury", 57.9), ("Venus", 108.2), ("Earth", 149.6 ), ("Mars", 227.9), ("Jupiter", 778.3))

planetDistanceFromSun.foreach{ tuple => {
  tuple match {
      case ("Mercury", distance) => println(s"Mercury is $distance millions km far from Sun")
      case p if(p._1 == "Venus") => println(s"Venus is ${p._2} millions km far from Sun")
      case p if(p._1 == "Earth") => println(s"Blue planet is ${p._2} millions km far from Sun")
      case _ => println("Too far....")
    }
  }
}

in ‘for’ comprehension

1
2
3
4
val numPairs = List((2, 5), (3, -7), (20, 56))
for ((a, b) <- numPairs) {
  println(a * b)
}

Users may sometimes find hard to choose between Tuples and case classes. As a rule, case classes are preferred choice if elements carry more meaning.

Class Composition with Mixins

Mixins are traits which are used to compose a class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
abstract class A {
  val message: String
}
class B extends A {
  val message = "I'm an instance of class B"
}
trait C extends A {
  def loudMessage = message.toUpperCase()
}
class D extends B with C

val d = new D
println(d.message)  // I'm an instance of class B
println(d.loudMessage)  // I'M AN INSTANCE OF CLASS B

Class D has a superclass B and a mixin C. Classes can only have one superclass but many mixins (using the keywords extends and with respectively). The mixins and the superclass may have the same supertype.

Higher-order Functions

Nested Methods

Multiple Parameter Lists (Currying)

Case Classes

1
2
3
case class Point(x: Int, y: Int)   // case classes are immutable and compared by value.
val point = Point(1, 2)            // instantiate case classes without new keyword.
val anotherPoint = Point(2, 2)
1
2
3
4
5
6

if (point == anotherPoint) {           // compared by value.
  println(point + " and " + anotherPoint + " are the same.")
} else {
  println(point + " and " + anotherPoint + " are different.")
}

Pattern Matching

Singleton Objects

Objects are single instances of their own definitions.

1
object O extends D { ... }                      // define a singleton. (module-like)

Regular Expression Patterns

Extractor Objects

For Comprehensions

Generic Classes

Variances

Upper Type Bounds

Lower Type Bounds

The term B >: A expresses that the type parameter B or the abstract type B refer to a supertype of type A. In most cases, Awill be the type parameter of the class and B will be the type parameter of a method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
trait Node[+B] {
  def prepend(elem: B): Node[B]
}

case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
  def prepend(elem: B): ListNode[B] = ListNode(elem, this)
  def head: B = h
  def tail: Node[B] = t
}

case class Nil[+B]() extends Node[B] {
  def prepend(elem: B): ListNode[B] = ListNode(elem, this)
}

This program does not compile because the parameter elem in prepend is of type B, which we declared covariant. This doesn’t work because functions are contravariant in their parameter types and covariant in their result types.

To fix this, we need to flip the variance of the type of the parameter elem in prepend. We do this by introducing a new type parameter U that has B as a lower type bound.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
trait Node[+B] {
  def prepend[U >: B](elem: U): Node[U]
}

case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
  def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
  def head: B = h
  def tail: Node[B] = t
}

case class Nil[+B]() extends Node[B] {
  def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
}

Now we can do the following:

1
2
3
4
5
6
7
trait Bird
case class AfricanSwallow() extends Bird
case class EuropeanSwallow() extends Bird

val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil())
val birdList: Node[Bird] = africanSwallowList
birdList.prepend(new EuropeanSwallow)

The Node[Bird] can be assigned the africanSwallowList but then accept EuropeanSwallows.

Inner Classes

In Scala inner classes are bound to the outer object.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Graph {
  class Node {
    var connectedNodes: List[Node] = Nil
    def connectTo(node: Node) {
      if (connectedNodes.find(node.equals).isEmpty) {
        connectedNodes = node :: connectedNodes
      }
    }
  }
  var nodes: List[Node] = Nil
  def newNode: Node = {
    val res = new Node
    nodes = res :: nodes
    res
  }
}

The class Node is a path-dependent type because it is nested in the class Graph. Therefore, all nodes in the connectedNodes must be created using the newNode from the same instance of Graph.

1
2
3
4
5
6
val graph1: Graph = new Graph
val node1: graph1.Node = graph1.newNode
val node2: graph1.Node = graph1.newNode
val node3: graph1.Node = graph1.newNode
node1.connectTo(node2)
node3.connectTo(node1)

The nodes of the other graph have a different type.

1
2
3
4
5
6
7
val graph1: Graph = new Graph
val node1: graph1.Node = graph1.newNode
val node2: graph1.Node = graph1.newNode
node1.connectTo(node2)      // legal
val graph2: Graph = new Graph
val node3: graph2.Node = graph2.newNode
node1.connectTo(node3)      // illegal!

Java let inner classes are members of the enclosing class. In Scala such a type can be expressed as well, it is written Graph#Node.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Graph {
  class Node {
    var connectedNodes: List[Graph#Node] = Nil
    def connectTo(node: Graph#Node) {
      if (connectedNodes.find(node.equals).isEmpty) {
        connectedNodes = node :: connectedNodes
      }
    }
  }
  var nodes: List[Node] = Nil
  def newNode: Node = {
    val res = new Node
    nodes = res :: nodes
    res
  }
}

Abstract Types

Traits and abstract classes can have an abstract type member. This means that the concrete implementations define the actual type.

1
2
3
4
trait Buffer {
  type T
  val element: T
}

We can extend this trait in an abstract class, adding an upper-type-bound to T to make it more specific.

1
2
3
4
5
abstract class SeqBuffer extends Buffer {
  type U
  type T <: Seq[U]
  def length = element.length
}

Traits or classes with abstract type members are often used in combination with anonymous class instantiations.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
abstract class IntSeqBuffer extends SeqBuffer {
  type U = Int
}

def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
  new IntSeqBuffer {
       type T = List[U]
       val element = List(elem1, elem2)
     }
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)

Here the factory newIntSeqBuf uses an anonymous class implementation of IntSeqBuf (i.e. new IntSeqBuffer), setting type T to a List[Int].

It is also possible to turn abstract type members into type parameters of classes and vice versa. Here is a version of the code above which only uses type parameters:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
abstract class Buffer[+T] {
  val element: T
}
abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] {
  def length = element.length
}

def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] =
  new SeqBuffer[Int, List[Int]] {
    val element = List(e1, e2)
  }

val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)

Note that we have to use variance annotations here (+T <: Seq[U]) in order to hide the concrete sequence implementation type of the object returned from method newIntSeqBuf. Furthermore, there are cases where it is not possible to replace abstract types with type parameters.

Compound Types

Sometimes it is necessary to express that the type of an object is a subtype of several other types. In Scala this can be expressed with the help of compound types, which are intersections of object types.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
trait Cloneable extends java.lang.Cloneable {
  override def clone(): Cloneable = {
    super.clone().asInstanceOf[Cloneable]
  }
}
trait Resetable {
  def reset: Unit
}
def cloneAndReset(obj: ?): Cloneable = {
  val cloned = obj.clone()
  obj.reset
  cloned
}

This compound type is written like this in Scala: Cloneable with Resetable.

1
2
3
def cloneAndReset(obj: Cloneable with Resetable): Cloneable = {
  //...
}

Self-type

Self-types are a way to declare that a trait must be mixed into another trait, even though it doesn’t directly extend it. That makes the members of the dependency available without imports.

A self-type is a way to narrow the type of this or another identifier that aliases this.

To use a self-type in a trait, write an identifier, the type of another trait to mix in, and a => (e.g. someIdentifier: SomeOtherTrait =>).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
trait User {
  def username: String
}

trait Tweeter {
  this: User =>  // reassign this
  def tweet(tweetText: String) = println(s"$username: $tweetText")
}

class VerifiedTweeter(val username_ : String) extends Tweeter with User {  
// We mixin User because Tweeter required it
	def username = s"real $username_"
}

val realBeyoncé = new VerifiedTweeter("Beyoncé")
realBeyoncé.tweet("Just spilled my glass of lemonade")  
// prints "real Beyoncé: Just spilled my glass of lemonade"

Because we said this: User => in trait Tweeter, now the variable username is in scope for the tweet method. This also means that since VerifiedTweeter extends Tweeter, it must also mix-in User(using with User).

Implicit Parameters

A method can have an implicit parameter list, marked by the implicit keyword at the start of the parameter list. If the parameters in that parameter list are not passed as usual, Scala will look if it can get an implicit value of the correct type, and if it can, pass it automatically.

The places Scala will look for these parameters fall into two categories:

  • Scala will first look for implicit definitions and implicit parameters that can be accessed directly (without a prefix) at the point the method with the implicit parameter block is called.
  • Then it looks for members marked implicit in all the companion objects associated with the implicit candidate type.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
abstract class Monoid[A] {
  def add(x: A, y: A): A
  def unit: A
}

object ImplicitTest {
  implicit val stringMonoid: Monoid[String] = new Monoid[String] {
    def add(x: String, y: String): String = x concat y
    def unit: String = ""
  }
  
  implicit val intMonoid: Monoid[Int] = new Monoid[Int] {
    def add(x: Int, y: Int): Int = x + y
    def unit: Int = 0
  }
  
  def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
    if (xs.isEmpty) m.unit
    else m.add(xs.head, sum(xs.tail))
    
  def main(args: Array[String]): Unit = {
    println(sum(List(1, 2, 3)))       // uses IntMonoid implicitly
    println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly
  }
}

To show how implicit parameters work, we first define monoids StringMonoid and IntMonoid for strings and integers, respectively. The implicit keyword indicates that the corresponding object can be used implicitly.

Making the parameter m implicit here means we only have to provide the xs parameter when we call the method if Scala can find a an implicit Monoid[A] to use for the implicit m parameter.

Implicit Conversions

Polymorphic Methods

Type Inference

Operators

In Scala, operators are methods. Any method with a single parameter can be used as an infix operator.

1
10.+(1)
1
10 + 1

Defining and using operators

You can use any legal identifier as an operator. This includes a name like add or a symbol(s) like +.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
case class Vec(val x: Double, val y: Double) {
  def +(that: Vec) = new Vec(this.x + that.x, this.y + that.y)
}

val vector1 = Vec(1.0, 1.0)
val vector2 = Vec(2.0, 2.0)

val vector3 = vector1 + vector2
vector3.x  // 3.0
vector3.y  // 3.0

Using parentheses, you can build up complex expressions with readable syntax.

1
2
3
4
5
6
7
8
case class MyBool(x: Boolean) {
  def and(that: MyBool): MyBool = if (x) that else this
  def or(that: MyBool): MyBool = if (x) this else that
  def negate: MyBool = MyBool(!x)
}

def not(x: MyBool) = x.negate
def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y)

Precedence

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(characters not shown below)
* / %
+ -
:
= !
< >
&
^
|
(all letters)

For example

1
a + b ^? c ?^ d less a ==> b | c

Is equivalent to

1
((a + b) ^? (c ?^ d)) less ((a ==> b) | c)

?^ has the highest precedence because it starts with the character ?. + has the second highest precedence, followed by ==>, ^?, |, and less.

By-name Parameters

By-name parameters are only evaluated when used. They are in contrast to by-value parameters. To make a parameter called by-name, simply prepend => to its type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def whileLoop(condition: => Boolean)(body: => Unit): Unit =
  if (condition) {
    body
    whileLoop(condition)(body)
  }

var i = 2

whileLoop (i > 0) {
  println(i)
  i -= 1
}  // prints 2 1

If the condition is false, the body is never evaluated because we prepended => to the type of body.

This ability to delay evaluation of a parameter until it is used can help performance if the parameter is computationally intensive to evaluate or a longer-running block of code such as fetching a URL.

Annotations

Default Parameter Values

Named Arguments

Packages and Imports