What we will learn in this lecture
Scala background
Expressions & Declarations & Objects
Types
Functions
Classes
Pattern Matching
The Name
Creation: Martin Odersky et al around 2004
Scala's Principles
Target-Systems
Why Scala: Java ecosystem
Why Scala: Java ecosystem
access to Java ecosystem
or any ecosystem compiling to ByteCode
JVM performance (JIT, GC)
... and tooling (metrics, debugging, etc.)
JVM in a nutshell
Why Scala: brings its own ecosysten
Why Scala: fusion of FP and OOP
fusion of FP and OOP
with additional Type-Level programming on top
choose paradigm which solve your problem best ...
... stay in the same language
Why Scala: industry & academia combined
big players use it: Twitter, Stripe to name a few
backed by Lightbend Inc.
backed by EPFL
... and of course by an Open Source community
Why not to use Scala: tooling
Why not to user Scala: learning curve
Objects
An object is a collection of values and a set of operations on that values.
// example: literal object
1
// value: integer 1
// operations: +, -, *, /, etc.
// example: literal object
"Hello"
// value: string Hello
// operations: +, split, take, etc.
// example: class object (tuple)
(1, "Hello")
// values: integer 1 and string Hello
// operations: _1, _2, swap, etc.
Expressions
Expression can be objects, combination of objects or functions applied to objects. And they get evaluated/reduced to results.
Objects as expressions
Objects
// already in reduced form
1
'a'
"Hello"
Operations as expressions
Function/Operation application
1 + 2
== 3
But how do we use the result?
Assignment of Results
val result = 1 + 2
// ^ ^ ^
// | | '
// | | assigns result to identifier
// | '
// | identifier
// '
// starts assignment
result == 3
Assignment of Results
val result = 1 + 2
// forbidden - `result` never changes
result = 2 + 3
Expressions
Okay, we can create single line expressions or assignments. But this is barely useful.
Block Expressions
When you want to combine multiple expressions you put them in blocks.
// start with '{' and end with '}'
{
val a = 1 + 2
3 * a
}
Block expressions
{
val a = 1 + 2
3 * a
}
{
val a = 3 <==
3 * a
}
{
val a = 3
3 * 3 <==
}
== 9
Scoping: values in blocks
Access objects in the same or an outer block.
{
val a = 1 + 2
// `a` in scope
{
val b = 3 + 4
a + b
}
}
Scoping: values in blocks
{
val a = 1 + 2
{
val b = 3 + 4
a + b
}
}
{
val a = 3 <==
{
val b = 7 <==
a + b
}
}
Scoping: values in blocks
{
val a = 3
{
val b = 7
3 + 7 <==
}
}
== 10
Block Expressions
From now on we ignore the outer block brackets in the slides.
Does it work?
val a = 5
{
val b = 3
a + b
}
== 8
Does it work?
val a = {
val b = 6
10
}
a + b
// no, `b` doesn't exist in the outer scope
Does it work?
val a = 5
{
val a = {
a * 10
}
a
}
// no, you try to define `a` by using itself
Does it work?
val a = 5
{
val a = 10
a
}
== 10
Conditional expression
val a = 5
if (a > 0) {
"positive number"
}
else if (a == 0) {
"is zero"
}
else {
"negative number"
}
Comparison
a > b // greater than
a < b // smaller than
a >= b // greater or equal
a <= b // smaller or equal
a == b // equal
a != b // equal
// more later on ...
Short Summary
So far we have ...
// object expressions
val PI = 3.14
val radius = 2.0
// function applications
val area = radius * radius * PI
// conditional expressions
if (area > 4.0) {
"big circle"
}
else {
"small circle"
}
Expression Evaluation
But in which order are these expressions evaluated exactly?
Expression Evaluation
from top to bottom
from left to right
before application
following precedence rules
Precedence
0 - (all letters)
1 - |
2 - ^
3 - &
4 - = !
5 - < >
6 - :
7 - + -
8 - * / %
// (all other special characters)
What's the result?
2 * 3 + 10
// prec('*') > prec('+')
== (2 * 3) + 10
== 6 + 10
== 16
What's the result?
2.0 * 3.0 / 3.0
// '*' left most operation
== (2.0 * 3.0) / 3.0
== 6.0 / 3.0
== 2.0
What's the result?
val a = 1 + 2
if (a > 1 + 3) {
"a = " + a
}
else {
"too small"
}
== {
if (3 > 4) {
"a = " + 3
}
else {
"too small"
}
}
== "too small"
1 // Int
"hello" // String
1.0 + 2.0 // reduces to a Double
Definition
State of a value
Restriction on objects.
val a: Int = 0
// `a` is of type `Int`
// is an Integer number
// -2147483648 <= a <= 2147483647
val b: String = a // forbidden
Operation restriction
Restriction on operations
1 + 2 == 3: Int
1 + 2.0 == 3.0: Double
1 + true // forbidden
Types
Types restrict the number of valid programs. Basically, it is harder for you to write invalid programs.
What is the type?
1 + 2
== 3: Int
1.0 + 2
== 3.0: Double
"Hello " + true
== "Hello true": String
What is the type?
1 / 2
== 0: Int
1 / 2.0
== 0.5: Double
Type: number types
Scala picks the more precise number type during operator application.
Types are important
guarantees about state of values / application of operators
proven during compile time
no extra tests necessary
Functions
Special type of expression which has a name, takes input and returns, as always, a result.
Functions
Functions
def plus(a: Int, b: Int): Int = a + b
// declaration
def plus(a: Int, b: Int): Int
// ^ ^ ^ ^
// | '-------| |
// identifier input result
// body expression
a + b
// ^ ^
// '---'
// |
// input values
Functions: val syntax
// functions are objects
// this becomes
def plus(a: Int, b: Int): Int = a + b
// that
val plus: (Int, Int) => Int = (a, b) => a + b
Functions
have a unique identifier (name)
declare a number of parameters as input (arity)
and a single result type
body expression fulfills the declaration
Function application
plus(1, 2) == 3
// `1` assigned to `a` and `2` to `b`
// every appearance of `a` and `b` is replaced by the objects
1 + 2 == 3
Curried Function
def plusCurried(a: Int): Int => Int = b => a + b
// equal to
def plusCurried(a: Int)(b: Int): Int = a + b
val plus1: Int => Int = plusCurried(1)
plus1(2) == plus(1, 2)
Curried Functions
this technique is called currying
apply a single parameter at a time (left to right) - it's called partial application
every application returns a new function of arity $n - 1$
Mix curried and uncurried
def plusPair(a: Int, b: Int)(c: Int, d: Int): Int = {
plus(a, c) + plus(b, d)
}
Higher-Order functions
def plus(a: Int, b: Int): Int = ???
// we can return function, we can pass functions - all just objects
def resultMsg(f: (Int, Int) => Int)(a: Int, b: Int): String = {
"The result for " + a + " and " + b + " is " + f(a, b)
}
val plusMsg = resultMsg(plus)
plusMsg(1, 2) == "The result for 1 and 2 is 3"
def multiply(a: Int, b: Int): Int = ???
val multMsg = resultMsg(multiply)
multMsg(1, 2) == "The result for 1 and 2 is 2"
Anonymous Functions
Sometimes a function parameter in a higher-order function is only used ones. So why providing a whole declaration?
Anonymous Functions
// just provide the expression body - no declaration
val subMsg = resultMsg((a, b) => a - b)
subMsg(1 , 2) == "The result of 1 and 2 is -1"
Let's code: Functions
# start sbt (keep it running)
$> sbt
# switch into exercise project
sbt> project scala101-exercises
# to compile your code
sbt> compile
# to test your implementation
sbt> test:testOnly exercise1.FunctionsSpec
Let's code
// package/path definition: <package>.<object-name> must be unique
package exercise1
object Functions {
// your code goes here
...
}
Let's code: HigherOrder
sbt> project scala101-exercises
sbt> test:testOnly exercise1.HigherOrderSpec
Create a new type
Create a new type
You create new types in Scala by defining Classes. A class is a way to define a set of objects which have same fields and functions.
class
case class
object
trait
abstract Class
Classes
class Person(val name: String, val age: Int)
// ^ ^ ^
// ' '-----------------'
// identifier '
// constructor with public fields
val user = new Person("Gandalf", 2019)
// ^
// '
// call constructor / create new object
user.name == "Gandalf"
user.age == 2019
Classes: private fields
class Person(val name: String, age: Int)
// ^
// '
// private field
val user = new Person("Gandalf", 2019)
user.name == "Gandalf"
user.age // not allowed
Classes: inner fields
class Person(val name: String, val age: Int) {
private val ageString = age.toString
val show = "person: " + name + ", " + ageString
}
new Person("Gandalf", 2019).show == "person: Gandalf, 2019"
val user = new Person("Gandalf", 2019)
user.show == "person: Gandalf, 2019"
user.ageString // not allowed
Classes: methods
Methods aka context sensitive functions.
class Person(val name: String, val age: Int) {
// functions refering to their object are called methods
def isOlder(other: Person): Boolean = age > other.age
// equal to
def isOlder(other: Person): Boolean = this.age > other.age
}
Classes: methods
Methods aka context sensitive functions.
// or
def isOlder(user: Person)(other: Person): Boolean =
user.age > other.age
val gandalf = new Person("Gandalf", 2019)
gandalf.isOlder(new Person("Frodo", 33)) == {
isOlder(gandalf)(new Person("Frodo", 33))
}
Classes: methods
Methods are functions which have their surrounding object in scope and belong to the class. They also provide a means to get infix notation.
Classes: immutable fields
All fields are `val`. Therefore, they cannot change after assignment. To change them you have to create a new object.
Case Classes
case class Person(name: String, age: Int)
// ^ ^ ^
// | '-------------'
// identifier |
// |
// public fields
// no `new` keyword any longer
val gandalf = Person("Gandalf", 2019)
Case Classes: How to mutate them?
// comes with a copy method
val gandalf = Person("Gandalf", 2019)
// creates copy of `gandalf`
gandalf.copy("Gandolf", 2000) == Person("Gandolf", 2000)
Case Classes: How to mutate them?
val gandalf = Person("Gandalf", 2019)
// change just a supset of fields
gandalf.copy(name = "Gandolf") == Person("Gandolf", 2019)
Functions: default parameter
Default parameters and by-name application
def newPerson(name: String = "Gandalf", age: Int = 2019): Person =
Person(name, age)
newPerson() == Person("Gandalf", 2019)
newPerson("Gandolf", 2000) == Person("Gandolf", 2000)
newPerson("Gandolf") == Person("Gandolf", 2019)
newPerson(age = 2000) == Person("Gandalf", 2000)
Case Objects
What if you want to represent some case which is static?
// this is static; will not change
// why create a new instance every time?
case class Green()
case class Red()
// use `case objects` instead
case object Green
case object Red
Objects
Objects are classes with a single instance. You use them to provide static fields and functions.
Objects
From now on we refer to object classes as Objects and object terms as Values.
Objects
object Predef {
// constant fields start with a uppercase letter
val Gandalf = Person("Gandalf", 2019)
def older(a: Person, b: Person): Person =
if (a.age > b.age) a else b
}
Predef.older(Person("Frodo", 33), Predef.Gandalf) == Predef.Gandalf
Companion Objects: a class's companion
Companion to a class.
case class Person(name: String, age: Int)
// same name as the class, case class, trait, abstract class
object Person {
val Gandalf = Person("Gandalf", 2019)
def isGandalf(person: Person): Boolean =
person.name == "Gandalf"
}
Traits
But what if we have multiple data types which share a relation?
Traits
// all people (of some magic realm) but of different type
case class Wizard(name: String, power: String)
case class Elf(name: String, age: Int)
case class Dwarf(name: String, height: Int)
Traits
sealed trait Person
// ^ ^
// ' '-------------------
// only allow sub classes '
// in this file (optional) identifier
case class Wizard(name: String, power: String) extends Person
case class Elf(name: String, age: Int) extends Person
case class Dwarf(name: String, height: Int) extends Person
Traits: common properties and behaviour
// all subclasses share this properties
sealed trait Person {
// expected field
val name: String
// expected function
def likesPlace(place: String): Boolean
// default implementation
def sameName(other: Person): Boolean = name == other.name
}
Traits: common properties and behaviour
case class Wizard(name: String, power: String) extends Person {
def likesPlace(place: String): Boolean = true
}
case class Elf(name: String, age: Int) extends Person {
def likesPlace(place: String): Boolean = place == "woods"
}
case class Darf(name: String, height: Int) extends Person {
def likesPlace(place: String): Boolean = place == "mountain"
}
val gandalf = Wizard("Gandalf", "magic")
gandalf.likesPlace("The Shire") == true
Abstract Classes
// non complete (abstract methods) class definition
abstract class Person(val name: String) {
def isOlder(person: Person): Boolean
}
Functions and Classes
Function values are instances of Function-Classes in Scala. A Function-Class is a class which provides an apply method.
Functions and Classes
class Plus() {
def apply(a: Int, b: Int): Int = a + b
}
val plus = new Plus()
plus.apply(1, 2) == plus(1, 2)
== 3
Functions and Classes
object Plus {
def apply(a: Int, b: Int): Int = a + b
}
Plus.apply(1, 2) == Plus(1, 2)
== 3
Functions and Classes: inheritence
object Plus extends ((Int, Int) => Int) {
def apply(a: Int, b: Int): Int = a + b
}
Plus.apply(1, 2) == Plus(1, 2)
== 3
OOP
That's because it is. Again Scala is the fusion of OOP and FP.
Let's code: Classes
sbt> project scala101-exercises
sbt> test:testOnly exercises1.ClassesSpec
expressions and declarations
functions and types
classes
Pattern Matching
match values against pattern
extract information
decide program control flow
Pattern Matching
val person: Person = ???
person match {
case Wizard(name, power) => s"this Wizards uses $power"
case Elf(name, age) => s"this Elf is $age years old"
case Dwarf(name, height) => s"this dwarf is $height cm tall"
}
Pattern Matching
val person = Wizard("Gandalf", "magic")
person match {
case Wizard(name, power) => s"this Wizards uses $power"
case Elf(name, age) => s"this Elf is $age years old"
case Dwarf(name, height) => s"this dwarf is $height cm tall"
}
person match {
case Wizard(name, power = "magic") => s"this Wizards uses $power"
// case Elf(name, age) => ...
//case Dwarf(name, height) => ...
}
Pattern Matching
val person = Dwarf("Gimli", 107 /*cm*/)
person match {
case Wizard(name, power) => s"this Wizards uses $power"
case Elf(name, age) => s"this Elf is $age years old"
case Dwarf(name, height) => s"this dwarf is $height cm tall"
}
person match {
// case Wizard(name, power) => ...
case Elf(name, age) => s"this Elf is $age years old"
case Dwarf(name, height) => s"this dwarf is $height cm tall"
}
person match {
// case Wizard(name, power) => ...
// case Elf(name, age) => ...
case Dwarf(name, height = 107) => s"this dwarf is $height cm tall"
}
Ommit values you don't need
val person: Person = ???
person match {
case Wizard(name, _) => name
...
}
Conditional cases
val person: Person = ???
person match {
// you can also use `&` and `|`
case Wizard(name, _) if name == "Gandalf" => "a wizard is never late"
...
}
Get the whole value
val person: Person = ???
person match {
// you can also use `&` and `|`
case w@Wizard(_, power) if power == "magic" => w
...
}
Match on everything
val person: Person = ???
person match {
case person: Person => person.name
}
Match on everything
val person: Person = ???
person match {
case Wizard(name, _) => "The mighty " + name
case person: Person => person.name
// can't reach this -> previous case captures everything
case Dwarf(_, _) => "you will never now"
}
Ignore match
val person: Person = ???
person match {
case Wizard(name, _) => "The mighty " + name
case _ => "I don't care"
}
Pattern Matching
Make it exhaustive. Don't miss a case.
Only declare fields you use.
Works also with primitives.
Match primitives
0 match {
case 5 => "yeah five"
case 0 => "nay zero"
case _ => "don't care"
}
Let's code: Pattern Matching
sbt> project scala101-exercises
sbt> test:testOnly exercise1.PatternMatchingSpec
Expressions
{
val a = 1 + 2
3 * a
}
== 15
Types
val a: String = "Hello"
val b: Int = a // no no
Functions
def plus(a: Int, b: Int): Int = a + b
plus(1, 2) == 3
Classes
sealed trait Persons {
val name: String
}
case class Wizard(name: String, power: String) extends Person
case class Elf(name: String, age: Int) extends Person
Pattern Matching
val person: Person = ???
person match {
case Wizard(name, _) => name
case Elf(name, _) => name
}