- Introduction
- Basics
- Unified Types
- Classes
- Tuples
- Class composition with Mixins
- Higher Order Functions
- Nested Methods
- Multiple Parameter Lists (Currying)
- Pattern matching
- Singleton objects
- Regular Expression Patterns
- For Comprehensions
- Generic Classes
- Variances
- Upper Type Bounds
- Lower Type Bounds
- Inner Classes
- Self-type
- Implicit Parameters
- Polymorphic Methods
- Operators
- By-name Parameters
- Default Parameter Values
- Packages and Imports
- More Notes
- References
This repo is a concise summary and replacement of the Tour of Scala tutorial. Using the hyperlinks below is optional.
Scala is both an object-oriented and functional programming language. It is a statically-typed language, but uses type inference so the user is not required to annotate code with redundant type information.
Online compiler: Scala Fiddle
val
is a constantvar
is a variable
val x = 2;
x = 3; // does not compile
var x = 2;
x = 3; // compiles
Types of values can be inferred, but you can also explicitly state the type:
val x: Int = 2
(x: Int) => x + 1 // anonymous (no-name) function
val getTheAnswer = () => 42 // named function (0 parameters)
val addOne = (x: Int) => x + 1 // named function (1 parameter)
val add = (x: Int, y: Int) => x + y // named function (2 parameters)
A block evaluates to the last line in the block.
println({
val x = 1 + 1
x + 1
}) // 3
For methods, the return
statement is not needed, since the value of the last line is returned.
0 parameters
def name: String = System.getProperty("user.name")
println("Hello, " + name + "!")
1 parameter list
def add(x: Int, y: Int): Int = x + y
println(add(1, 2)) // 3
2 parameter lists
def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier
println(addThenMultiply(1, 2)(3)) // 9
Unit
is like void
in Java
class Greeter(prefix: String, suffix: String) {
def greet(name: String): Unit =
println(prefix + name + suffix)
}
val greeter = new Greeter("Hello, ", "!")
greeter.greet("Scala developer") // Hello, Scala developer!
case classes are immutable and compared by value
case class Point(x: Int, y: Int)
The new
keyword is not necessary when creating a Case Class
val point = Point(1, 2)
val anotherPoint = Point(1, 2)
if (point == anotherPoint) {
println("same point") // this will print
}
The object keyword is used to create a singleton. This feature replaces the static
keyword in Java.
object IdFactory {
private var counter = 0
def create(): Int = {
counter += 1
counter
}
}
val newId: Int = IdFactory.create()
println(newId) // 1
val newerId: Int = IdFactory.create()
println(newerId) // 2
Traits are like interfaces in Java
trait Greeter {
def greet(name: String): Unit
}
Traits can have default implementations:
trait Greeter {
def greet(name: String): Unit =
println("Hello, " + name + "!")
}
- Extend traits using
extends
keyword - Override an implementationusing
override
keyword.
class DefaultGreeter extends Greeter
class CustomizableGreeter(prefix: String, postfix: String) extends Greeter {
override def greet(name: String): Unit = {
println(prefix + name + postfix)
}
}
val greeter = new DefaultGreeter()
greeter.greet("Scala developer") // Hello, Scala developer!
val customGreeter = new CustomizableGreeter("How are you, ", "?")
customGreeter.greet("Scala developer") // How are you, Scala developer?
Syntax for Scala's main method:
object Main {
def main(args: Array[String]): Unit =
println("Hello, Scala developer!")
}
Setters use special syntax: _=
class Point {
private var _x = 0
private val bound = 100
// Getter
def x = _x
// Setter
def x_= (newValue: Int): Unit = {
if (newValue < bound) _x = newValue else printWarning
}
private def printWarning = println("WARNING: Out of bounds")
}
val point1 = new Point
point1.x = 99
point1.y = 101 // prints the warning
Alternative explanation on getters/setters
val ingredient = ("Sugar" , 25)
println(ingredient._1) // Sugar
println(ingredient._2) // 25
Grabbing values out of the tuple:
val (name, quantity) = ingredient
println(name) // Sugar
println(quantity) // 25
Pattern matching using foreach
and case
:
val planets = List(("Mercury", 57.9), ("Venus", 108.2), ("Earth", 149.6))
planets.foreach {
case ("Earth", distance) =>
println(s"Our planet is $distance million kilometers from the sun")
case _ =>
}
Pattern matching in for
loops:
val numPairs = List((2, 5), (3, -7))
for ((a, b) <- numPairs) {
println(a * b)
}
Variables in tuples don't have names. If you want them to have names, use a "case class" instead.
Example: case class Planet(name: String, distance: Double)
This is how you can extend a subclass, and also have a "trait":
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
Higher order functions are functions that take another function as a parameter.
val salaries = Seq(2, 7, 4)
val newSalaries = salaries.map(x => x * 2) // List(4, 14, 8)
You an alternatively write the 2nd line as:
val newSalaries = salaries.map(_ * 2)
You can nest methods in Scala.
Main use cases:
- Passing a value to the 1st parameter list helps the 2nd parameter list infer the type
- Partial application
Here is an example of "partial application" that lets us define B
as List[Int]
:
def foldLeft[B](z: B)(op: (B, A) => B): B
val numbers = List(1, 2, 3, 4, 5)
val numberFunc = numbers.foldLeft(List[Int]()) _
val squares = numberFunc((xs, x) => xs :+ x*x)
print(squares) // List(1, 4, 9, 16, 25)
val cubes = numberFunc((xs, x) => xs :+ x*x*x)
print(cubes) // List(1, 8, 27, 64, 125)
"Switch statements" from Java are called "match expressions" in Scala.
abstract class Notification
case class Email(sender: String, title: String, body: String) extends Notification
case class SMS(caller: String, message: String) extends Notification
def showNotification(notification: Notification): String = {
notification match {
case Email(sender, title, _) =>
s"You got an email from $sender with title: $title"
case SMS(number, message) =>
s"You got an SMS from $number! Message: $message"
}
}
val someSms = SMS("12345", "Are you there?")
println(showNotification(someSms)) // prints You got an SMS from 12345! Message: Are you there?
Pattern guards are boolean expressions that make cases more specific:
def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = {
notification match {
case Email(sender, _, _) if importantPeopleInfo.contains(sender) =>
"You got an email from special someone!"
case SMS(number, _) if importantPeopleInfo.contains(number) =>
"You got an SMS from special someone!"
case other =>
showNotification(other) // nothing special, delegate to our original showNotification function
}
}
- matching on type - You can also match on "type" of the object
sealed
- Traits and classes can be markedsealed
which means all subtypes must be declared in the same file. This assures all subtypes are known. This is useful for pattern matching because we don't need a "catch all" case.
Scala uses companion objects instead of Java's static
keyword.
An object with the same name as a class is called a companion object. Conversely, the class is the object’s companion class. A companion class or object can access the private members of its companion.
Use a companion object for methods and values which are not specific to instances of the companion class.
import scala.math._
case class Circle(radius: Double) {
import Circle._
def area: Double = calculateArea(radius)
}
object Circle {
private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0)
}
val circle1 = Circle(5.0)
circle1.area
Any string can be converted to a regular expression using the .r
method.
import scala.util.matching.Regex
val numberPattern: Regex = "[0-9]".r
numberPattern.findFirstMatchIn("awesomepassword") match {
case Some(_) => println("Password OK")
case None => println("Password must contain a number")
}
for each
loop example:
case class User(name: String, age: Int)
val userBase = List(User("Travis", 28),
User("Kelly", 33),
User("Jennifer", 44),
User("Dennis", 23))
val twentySomethings = for (user <- userBase if (user.age >= 20 && user.age < 30))
yield user.name // i.e. add this to a list
twentySomethings.foreach(name => println(name)) // prints Travis Dennis
for each
loop example with 2 iterators:
def foo(n: Int, v: Int) =
for (i <- 0 until n;
j <- 0 until n if i + j == v)
println(s"($i, $j)")
foo(10, 10) // prints (1, 9) (2, 8) (3, 7) (4, 6) (5, 5) (6, 4) (7, 3) (8, 2) (9, 1)
A generic Stack
:
class Stack[A] {
private var elements: List[A] = Nil
def push(x: A) { elements = x :: elements }
def peek: A = elements.head
def pop(): A = {
val currentTop = peek
elements = elements.tail
currentTop
}
}
val stack = new Stack[Int]
stack.push(1)
stack.push(2)
println(stack.pop) // prints 2
println(stack.pop) // prints 1
class Foo[+A] // Covariant class
class Bar[-A] // Contravariant class
class Baz[A] // Invariant class
For some class List[+A]
, making A
covariant implies that for two types A
and B
where A
is a subtype of B
, then List[A]
is a subtype of List[B]
- Given
A
isAnimal
B
isCat
Animal
is a subtype ofCat
- Then
List[Animal]
is a subtype ofList[Cat]
Scala's List
class is sealed abstract class List[+A]
, where the type parameter A
is covariant
Opposite of Covariance.
Let's say we literally had a printer that prints Animal
s. Then it should be able to print Cat
s as well. Covariance can help us model this scenario.
- Given
A
isAnimal
B
isCat
Animal
is a subtype ofCat
abstract class Printer[-A]
AnimalPrinter
extendsPrinter[Animal]
CatPrinter
extendsPrinter[Cat]
def printMyCat(printer: Printer[Cat])
- Then
Printer[Animal]
can be passed intoprintMyCat
Generic classes in Scala are invariant by default. This means that they are neither covariant nor contravariant.
Example: If we make a custom Container
class, then Container[Cat]
is not a Container[Animal]
. The reverse is not true either.
An upper type bound T <: A
declares that type variable T
refers to a subtype of type A
- Given
Cat
extendsPet
Lion
extendsAnimal
class PetContainer[P <: Pet](p: P) { def pet: P = p }
- Then
new PetContainer[Cat](new Cat)
compilesnew PetContainer[Lion](new Lion)
fails to compile
Common Pitfall: "Variances" and "Upper Type Bounds" are 2 different concepts. Notice you cannot replace P <: Pet
with +P
since then new PetContainer[Lion](new Lion)
would incorrectly succeed.
Opposite of Upper Type Bounds.
Common pitfall: "functions are contravariant in their parameter types and covariant in their result types". When making a type covariant by using +B
, we will run into a problem when using B
as a "parameter type" to a function. This is solved by using U >: B
instead. See Lower Type Bounds for a full example.
Scala allows classes to have other classes as members.
If you have 2 traits: Cloneable
and Resetable
, then the syntax for a function to take an object with those 2 traits is:
def cloneAndReset(obj: Cloneable with Resetable): Cloneable = {
//...
}
If it was 3 or more traits, the syntax would be Trait1 with Trait2 with Trait3
Unlikely I'll use this often. Self-types are used when 1 trait depends on another trait, but doesn't "extend" it. See Self-type for an example.
This is when we mark a variable implicit
in it's declaration, and implicit
where it's used in a function, and the compiler will try to match the 2 together.
- Given:
- value:
implicit val stringMonoid: Monoid[String]
- value:
implicit val intMonoid: Monoid[Int]
- method definition:
def sum[A](xs: List[A])(implicit m: Monoid[A]): A
- value:
- Then
m
will be matched withintMonoid
if we callsum(List(1, 2, 3))
See Implicit Parameters for the full example.
The compiler knows businessName
is a String:
val businessName = "Montreux Jazz Café"
The compiler knows an Int
will be returned:
def squareOf(x: Int) = x * x
For recursive functions, the compiler can't know the return type. The following code will fail:
def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1)
Compiler can also infer the types:
case class MyPair[A, B](x: A, y: B)
val p = MyPair(1, "scala") // type: MyPair[Int, String]
def id[T](x: T) = x
val q = id(1) // type: Int
It's easy to "overload" an operator:
case class Vec(x: Double, y: Double) {
def +(that: Vec) = 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
By-name parameters are only evaluated when used. They are in contrast to by-value parameters. To make a parameter called by-name, prepend =>
to its type:
def calculate(input: => Int) = input * 37
By-name parameters have the advantage that they are not evaluated if they aren’t used in the function body. On the other hand, by-value parameters have the advantage that they are evaluated only once.
This ability to delay evaluation of a parameter until it is used can help performance if the parameter is computationally intensive to evaluate.
class Point(val x: Double = 0, val y: Double = 0)
val point0 = new Point(1); // point (1, 0)
val point1 = new Point(y = 3) // point (0, 3)
For point1
, we use y=3
(a named argument) since "if the caller omits an argument, any following arguments must be named."
import users._ // import everything from the users package
import users.User // import the class User
import users.{User, UserPreferences} // Only imports selected members
import users.{UserPreferences => UPrefs} // import and rename for convenience
Class constructor: private, public, read-only, mutable variables:
class Ok[T](statusCode: Int, result: T) // private fields, but present on the constructor
class Ok[T](val statusCode: Int, val result: T) // public, read-only fields
class Ok[T](var statusCode: Int, var result: T) // public, mutable fields
On a case class, "when you use the case keyword, you do not need to use val
to make a field public and read-only":
case class Ok[T](statusCode: Int, result: T)
Null
– it's a Trait.null
– it's an instance ofNull
- similar to Java null.Nil
– represents an empty List of anything of zero length.Nothing
- it's a Trait. Its a subtype of everything, but not superclass of anything. There are no instances ofNothing
.None
– used withOption
which has exactly 2 subclasses:Some
andNone
.None
is used to represent a sensible return value to avoid null pointer exceptions.Unit
– type used in method that doesn’t return a value.
In Scala, it is encouraged to never use the return
keyword. A return
expression, when evaluated, abandons the current computation and returns to the caller of the method in which return appears:
Correct:
def sum(ns: Int*): Int = ns.foldLeft(0)((n, m) => n + m)
sum(33, 42, 99)
// Output
res2: Int = 174
Incorrect: was expecting 174, but got 33.
def sumR(ns: Int*): Int = ns.foldLeft(0)((n, m) => return n + m)
sumR(33, 42, 99)
// Output
res3: Int = 33
lazy val
is a language feature where the initialization of a val
is delayed until it is accessed for the first time. After that point, it acts just like a regular val
.
There are 2 reasons to use lazy val
in Scala":
- Initialization is computationally expensive and
val
is not always used
lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum}
if (scala.util.Random.nextInt > 1000) {
println(tiresomeValue)
}
- Resolving cyclic dependencies
Let's look at an example with two objects that need to be declared at the same time during instantiation:
object comicBook {
def main(args:Array[String]): Unit = {
gotham.hero.talk()
gotham.villain.talk()
}
}
class Superhero(val name: String) {
lazy val toLockUp = gotham.villain
def talk(): Unit = {
println(s"I won't let you win ${toLockUp.name}!")
}
}
class Supervillain(val name: String) {
lazy val toKill = gotham.hero
def talk(): Unit = {
println(s"Let me loosen up Gotham a little bit ${toKill.name}!")
}
}
object gotham {
val hero: Superhero = new Superhero("Batman")
val villain: Supervillain = new Supervillain("Joker")
}
By using lazy
, the reference can be assigned before it is initialized, without fear of having an uninitialized value.
Why not declare all vals lazy? - Lazy initialization is thread safe, so declaring all val
s as lazy would incur the overhead of ensuring thread safety, which is often not needed and would result in some unnecessary overhead for the usual case.
- Notes are summarized from Tour of Scala. All sections in their tutorial (other than "Implicit Conversions") were well written.
- Article: Nothingness - summarized above.
- Article: The Point of No Return - went through first 2 examples, which are summarized above.
- Article: Scala Language - lazy val - summarized above
- Reddit post: When to use 'lazy' - summarized most popular answer