Skip to the content.

← Back to Index

Scala Cheat Sheet

Evaluation Rules

Call by value: evaluates the function arguments before calling the function

Call by name: evaluates the function first, and then evaluates the arguments if need be

def example = 2      // evaluated when called
val example = 2      // evaluated immediately
lazy val example = 2 // evaluated once when needed

def square(x: Double)    // call by value
def square(x: => Double) // call by name
def myFct(bindings: Int*) =  ...  // bindings is a sequence of int, containing a varying # of arguments

Higher order functions

These are functions that take a function as a parameter or return functions.

// sum takes a function that takes an integer and returns an integer then
// returns a function that takes two integers and returns an integer
def sum(f: Int => Int): (Int, Int) => Int =
def sumf(a: Int, b: Int): Int = f(a) + f(b)
sumf

// same as above. Its type is (Int => Int) => (Int, Int) => Int
def sum(f: Int => Int)(a: Int, b: Int): Int =  ...

// Called like this

Currying

Converting a function with multiple arguments into a function with a single argument that returns another function.

val f2: (Int, Int) => Int = f // uncurried version (type is (Int, Int) => Int)
val f3: Int => Int => Int = f2.curried // transform it to a curried version (type is Int => Int => Int)
val f4: (Int, Int) => Int = Function.uncurried(f3) // go back to the uncurried version (type is (Int, Int) => Int)

Classes

class MyClass(x: Int, val y: Int,
var z: Int):        // Defines a new type MyClass with a constructor
// x will not be available outside MyClass
// val will generate a getter for y
// var will generate a getter and a setter for z
require(y > 0, "y must be positive")    // precondition, triggering an IllegalArgumentException if not met
def this (

this references the current object, assert(<condition>) issues AssertionError if condition is not met. See scala.Predef for require, assume and assert.


End markers

When the body of a class, object, trait, method or value becomes long, visually inspecting where it ends might become challenging. In these situations, it is possible to explicitly signal to the reader that the body is over using the end keyword with the name of the definition:

class MyClass(a: Int, b: String):
// body
end MyClass

object MyObject:
// body
end MyObject

object MyTrait:
// body

Operators

myObject myMethod 1 is the same as calling myObject.myMethod(1)

Operator (i.e. function) names can be alphanumeric, symbolic (e.g. x1, *, +?%&, vector_++, counter_=)

The precedence of an operator is determined by its first character, with the following increasing order of priority:

(all letters)
|
^
&
< >
= !
:
+ -
* / %
(all other special characters)

The associativity of an operator is determined by its last character: Right-associative if ending with :, Left-associative otherwise.

Note that assignment operators have lowest precedence.


Class hierarchies

abstract class TopLevel:     // abstract class
def method1(x: Int): Int   // abstract method
def method2(x: Int): Int =  ...
end TopLevel

class Level1 extends TopLevel:
def method1(x: Int): Int = ...
override def method2(x: Int): Int = ... // TopLevel's method2 needs to be explicitly overridden
end Level1

To create an runnable application in Scala:

@main def run(args: Array[String]) =
println("Hello world")

or

object Hello extends App:
println("Hello World")

Class Organization

trait Planar:
...
class Square extends Shape with Planar

Type Parameters

Similar to C++ templates or Java generics. These can apply to classes, traits or functions.

class MyClass[T](arg1: T):
...

    MyClass[Int](1)
    MyClass(1)   // the type is being inferred, i.e. determined based on the value arguments

It is possible to restrict the type being used, e.g.

def myFct[T <: TopLevel](arg: T): T = ... // T must derive from TopLevel or be TopLevel
def myFct[T >: Level1](arg: T): T = ...   // T must be a supertype of Level1
def myFct[T >: Level1 <: TopLevel](arg: T): T = ...

Variance

Given A <: B

If C[A] <: C[B], C is covariant

If C[A] >: C[B], C is contravariant

Otherwise C is nonvariant

class C[+A]  // C is covariant
class C[-A]  // C is contravariant
class C[A]   // C is nonvariant

For a function, if A2 <: A1 and B1 <: B2, then A1 => B1 <: A2 => B2.

Functions must be contravariant in their argument types and covariant in their result types, e.g.

trait Function1[-T, +U]:
  def apply(x: T): U
// Variance check is OK because T is contravariant and U is covariant

class Array[+T]:
  def update(x: T) // variance checks fails

Pattern Matching

Pattern matching is used for decomposing data structures:

unknownObject match
  case MyClass(n) => ...
  case MyClass2(a, b) => ...

Here are a few example patterns

(someList: List[T]) match
  case Nil => ...          // empty list
  case x :: Nil => ...     // list with only one element
  case List(x) => ...      // same as above
  case x :: xs => ...      // a list with at least one element. x is bound to the head,
  // xs to the tail. xs could be Nil or some other list.
  case 1 :: 2 :: cs => ... // lists that starts with 1 and then 2
  case (x, y) :: ps => ... // a list where the head element is a pair
  case _ => ...

The last example shows that every pattern consists of sub-patterns: it only matches lists with at least one element, where that element is a pair. x and y are again patterns that could match only specific types.

Options

Pattern matching can also be used for Option values. Some functions (like Map.get) return a value of type Option[T] which is either a value of type Some[T] or the value None:

val myMap = Map("a" -> 42, "b" -> 43)

def getMapValue(s: String): String = 
  myMap get s match
    case Some(nb) => "Value found: " + nb
    case None => "No value found"

getMapValue("a")  // "Value found: 42"
getMapValue("c")  // "No value found"

Most of the time when you write a pattern match on an option value, the same expression can be written more concisely using combinator methods of the Option class. For example, the function getMapValue can be written as follows:

def getMapValue(s: String): String =
  myMap.get(s).map("Value found: " + _).getOrElse("No value found")

Pattern Matching in Anonymous Functions

Pattern matching is also used quite often in anonymous functions:

val options: List[Option[Char]] = Some('a') :: None :: Some('b') :: Nil
val chars: List[Char] = pairs.map(p => p match {
case Some(ch) => ch
case None => 'z'
})

Instead of p => p match { case ... }, you can simply write { case ...}, so the above example becomes more concise:

val chars: List[Char] = pairs.map {
  case Some(ch) => ch
  case None => 'z'
}

Collections

Scala defines several collection classes:

Base Classes

Immutable Collections

Mutable Collections

Examples

val fruitList = List("apples", "oranges", "pears")
// Alternative syntax for lists
val fruit = "apples" :: ("oranges" :: ("pears" :: Nil)) // parens optional, :: (pronounced cons) is right-associative
fruit.head   // "apples"
fruit.tail   // List("oranges", "pears")
val empty = List()
val empty = Nil

val nums = Vector("louis", "frank", "hiromi")
nums(1)                     // element at index 1, returns "frank", complexity O(log(n))
nums.updated(2, "helena")   // new vector with a different string at index 2, complexity O(log(n))

val fruitSet = Set("apple", "banana", "pear", "banana")
fruitSet.size    // returns 3: there are no duplicates, only one banana

val r: Range = 1 until 5 // 1, 2, 3, 4
val s: Range = 1 to 5    // 1, 2, 3, 4, 5
1 to 10 by 3  // 1, 4, 7, 10
6 to 1 by -2  // 6, 4, 2

val s = (1 to 6).toSet
s.map(_ + 2) // adds 2 to each element of the set

val s = "Hello World"
s.filter(c => c.isUpper) // returns "HW"; strings can be treated as Seq[Char]

// Operations on sequences
val xs = List(...)
xs.length         // number of elements, complexity O(n)
xs.last           // last element (exception if xs is empty), complexity O(n)
xs.init           // all elements of xs but the last (exception if xs is empty), complexity O(n)
xs.take(n)        // first n elements of xs
xs.drop(n)        // the rest of the collection after taking n elements
xs.splitAt(n)     // same as (xs take n, xs drop n)
xs(n)             // the nth element of xs, complexity O(n)
xs ++ ys          // concatenation, complexity O(n)
xs.reverse        // reverse the order, complexity O(n)
xs.updated(n, x)  // same list than xs, except at index n where it contains x, complexity O(n)
xs.indexOf(x)     // the index of the first element equal to x (-1 otherwise)
xs.contains(x)    // same as xs indexOf x >= 0
xs.filter(p)      // returns a list of the elements that satisfy the predicate p
xs.filterNot(p)   // filter with negated p
xs.partition(p)   // same as (xs filter p, xs filterNot p)
xs.takeWhile(p)   // the longest prefix consisting of elements that satisfy p
xs.dropWhile(p)   // the remainder of the list after any leading element satisfying p have been removed
xs.span(p)        // same as (xs takeWhile p, xs dropWhile p)

List(x1, ..., xn).reduceLeft(op)   // (...(x1 op x2) op x3) op ...) op xn
List(x1, ..., xn).foldLeft(z)(op)  // (...( z op x1) op x2) op ...) op xn
List(x1, ..., xn).reduceRight(op)  // x1 op (... (x{n-1} op xn) ...)
List(x1, ..., xn).foldRight(z)(op) // x1 op (... (    xn op  z) ...)

xs.exists(p)   // true if there is at least one element for which predicate p is true
xs.forall(p)   // true if p(x) is true for all elements
xs.zip(ys)     // returns a list of pairs which groups elements with same index together
xs.unzip       // opposite of zip: returns a pair of two lists
xs.flatMap(f)  // applies the function to all elements and concatenates the result
xs.sum         // sum of elements of the numeric collection
xs.product     // product of elements of the numeric collection
xs.max         // maximum of collection
xs.min         // minimum of collection
xs.flatten     // flattens a collection of collection into a single-level collection
xs.groupBy(f)  // returns a map which points to a list of elements
xs.distinct    // sequence of distinct entries (removes duplicates)

x +: xs  // creates a new collection with leading element x
xs :+ x  // creates a new collection with trailing element x

// Operations on maps
val myMap = Map("I" -> 1, "V" -> 5, "X" -> 10)  // create a map
myMap("I")      // => 1
myMap("A")      // => java.util.NoSuchElementException
myMap.get("A")  // => None
myMap.get("I")  // => Some(1)
myMap.updated("V", 15)  // returns a new map where "V" maps to 15 (entry is updated)
// if the key ("V" here) does not exist, a new entry is added

// Operations on LazyLists
val xs = LazyList(1, 2, 3)
val xs = LazyList.cons(1, LazyList.cons(2, LazyList.cons(3, LazyList.empty))) // same as above
(1 to 1000).to[LazyList] // => Stream(1, ?)
x #:: xs // Same as LazyList.cons(x, xs)
// In the LazyList's cons operator, the second parameter (the tail)
// is defined as a "call by name" parameter.
// Note that x::xs always produces a List
def integers(start: Int = 0): LazyList[Int] = start #:: integers(start + 1) // infinite sequence of integers starting at "start"
integers(0).drop(10).take(100) // New lazylist starting at 10                   // element at index 1, returns "frank", complexity O(log(n))

Pairs (similar for larger Tuples)

val pair = ("answer", 42)   // type: (String, Int)
val (label, value) = pair   // label = "answer", value = 42  
pair(0) // "answer"  
pair(1) // 42

Ordering

There is already a class in the standard library that represents orderings: scala.math.Ordering[T] which contains comparison functions such as lt() and gt() for standard types. Types with a single natural ordering should inherit from the trait scala.math.Ordered[T].

import math.Ordering

def msort[T](xs: List[T])(using Ordering[T]) = ...
msort(fruits)(using Ordering.String)
msort(fruits)  // the compiler figures out the right ordering

For-Comprehensions

A for-comprehension is syntactic sugar for map, flatMap and filter operations on collections.

Example 1

// list all combinations of numbers x and y where x is drawn from
// 1 to M and y is drawn from 1 to N
for (x <- 1 to M; y <- 1 to N)
  yield (x, y)

is equivalent to

(1 to M).flatMap(x => (1 to N) map (y => (x, y)))

Translation Rules

A for-expression looks like a traditional for loop but works differently internally

Note: s is a (potentially empty) sequence of fields (generators and filters)

This means you can use a for-comprehension for your own type, as long as you define map, flatMap and filter.

Example 2

for
  i <- 1 until n  
  j <- 1 until i  
  if isPrime(i + j)  
yield (i, j)

is equivalent to

for (i <- 1 until n; j <- 1 until i if isPrime(i + j))
  yield (i, j)  

is equivalent to

(1 until n).flatMap(i => (1 until i).filter(j => isPrime(i + j)).map(j => (i, j)))