SCALAWORKSHOPKRAKOW
OCTOBER2017-DAY1
Fredrik Vraalsen - Data Platform
1
TOPICS
• Language features
• Functional programming
• Best practices
2
AGENDADAY1-THEBASICS
• Intro
• Scala basics
• Expressions, methods and functions
• Collections I
3
AGENDADAY2-FP
• Classes and Pattern Matching
• Collections II
• Laziness, implicits, DSLs
• QA & Wrapup
4
SCALARESOURCES
• API docs: https://www.scala-lang.org/api/current/
• Guides and overviews: http://docs.scala-lang.org/overviews/
• Tutorials: http://docs.scala-lang.org/tutorials/
5
TWITTER-EFFECTIVESCALA
• Best practices
• (Formatting)
• Usage of language features
• http://twitter.github.io/effectivescala/
6
BEFOREWESTART
7
git clone https://github.schibsted.io/fredrik-vraalsen/scala-workshop.git
SCALA
• Hybrid FP + OO language
• Developed at EPFL by Martin Odersky
• First public release in 2004, 2.0 in 2006
8
SCALA
9
https://dzone.com/articles/scala-liftoff
SCALA
• Current release 2.12 in 2016
• Focus on Java 8 interop
• Scala 3.0 in the works (codename Dotty)
10
SCALABASICS
HELLOWORLD
~ $ cat > helloworld.scala
println("Hello world!")
^D
~ $
12
HELLOWORLD
~ $ cat > helloworld.scala
println("Hello world!")
^D
~ $ scala helloworld.scala
Hello world!
~ $

13
LITERALS
"Fredrik" // String
'c' // Char
true / false // Boolean
42 // Int
2147483648L // Long
3.1415927f // Float
3.141592653589793 // Double
Byte (8 bit)
Short (16 bit)
14
VALUES
val name: String = "Fredrik"

val age: Int = 41
15
TYPEINFERENCE
val name = "Fredrik" // String
val age = 41 // Int
16
VARIABLES
val name = "Fredrik" // String
val age = 41 // Int
age = 42 // Compile error!
17
VARIABLES
val name = "Fredrik" // String
var age = 41 // Int
age = 42 // Yay!!?
18
STRINGS
val greeting = "Hello " + name + "!"
val greeting = s"Hello $name!"
val greeting = "Have some more %.5f!".format(Math.PI)
val greeting = s"""Hello "$name!"
This is a
multiline greeting!"""
19
STRINGS
val greeting = "Hello " + name + "!"
val greeting = s"Hello $name!"
val greeting = "Have some more %.5f!".format(Math.PI)
val greeting = s"""|Hello "$name!"
|This is a
|multiline greeting!""".stripMargin
20
CASECLASSES
case class Person(name: String, age: Int)
val p = Person("Fredrik", 41)
p.name // "Fredrik"
p.age // 41
21
EQUALITY
val p1 = Person("Fredrik", 41)
val p2 = Person("Fredrik", 41)
p1 == p2 // true
p1 eq p2 // false
p1.name == p2.name // true
p1.name eq p2.name // true
p1.age == p2.age // true
p1.age eq p2.age // error: value eq is not a member of Int
22
REPL
~ $ scala
Welcome to Scala 2.12.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_152).
Type in expressions for evaluation. Or try :help.
scala>
23
REPL
~ $ scala
Welcome to Scala 2.12.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_152).
Type in expressions for evaluation. Or try :help.
scala> case class Person(name: String, age: Int)
defined class Person
scala>
24
REPL
~ $ scala
Welcome to Scala 2.12.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_152).
Type in expressions for evaluation. Or try :help.
scala> case class Person(name: String, age: Int)
defined class Person
scala> val p = Person("Fredrik", 41)
p: Person = Person(Fredrik,41)
scala>
25
REPL
~ $ scala
Welcome to Scala 2.12.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_152).
Type in expressions for evaluation. Or try :help.
scala> case class Person(name: String, age: Int)
defined class Person
scala> val p = Person("Fredrik", 41)
p: Person = Person(Fredrik,41)
scala> p.name
res0: String = Fredrik
scala>
26
SCALATEST
• Multiple testing styles (xUnit, BDD, etc.)
• Asserts or matchers
27
SCALATEST
28
SCALATEST
29
SCALATEST
result should be (3)
result shouldBe 3
result should equal (3)
result shouldEqual(3)
result should === (3)
result should not be 3
result should !== (3)
result should be < 7
result should have length 3 // or size
result should startWith (“Hello”) // or endWith / contain
30
SCALATEST
• Run tests
• With Gradle: ./gradlew test -t
• In IDE: Ctrl+Shift+F10 or Ctrl+Shift+R
• More info at http://scalatest.org/
31
EXERCISE1
32
EXPRESSIONS,METHODSAND
FUNCTIONS
EXPRESSIONS
• Every expression returns a value
34
EXPRESSIONS
var sign: String = _ // null
if (i >= 0) {
sign = "Positive"
} else {
sign = "Negative"
}
35
EXPRESSIONS
val sign: String = if (i >= 0) {
"Positive"
} else {
"Negative"
}
36
EXPRESSIONS
val sign = if (i >= 0) {
"Positive"
} else {
"Negative"
}
37
EXPRESSIONS
val sign = if (i >= 0) "Positive" else "Negative"
38
EXPRESSIONS
val sign = if (i >= 0) "Positive" else “Negative"
String sign = i >= 0 ? "Positive" : “Negative" // Java
39
EXPRESSIONS
val evenNumbers = collection.mutable.ArrayBuffer[Int]()
for (i <- 1 to 10) evenNumbers.append(i * 2)
40
EXPRESSIONS
val evenNumbers = for (i <- 1 to 10) yield i * 2
val res = try callMethod() catch { case ex => defaultValue }


val avg = {

val sum = numbers.sum

sum / numbers.size

}

41
METHODS
42
METHODS
def add(a: Int, b: Int): Int = {

return a + b;

}
43
Keyword
Name Parameters Returntype
Body
METHODS
def add(a: Int, b: Int): Int = {

return a + b

}
44
Keyword
Name Parameters Returntype
Body
METHODS
def add(a: Int, b: Int): Int = {

a + b

}
45
Keyword
Name Parameters Returntype
Body
METHODS
def add(a: Int, b: Int): Int = a + b
46
Keyword
Name Parameters Returntype
Body
METHODS
def add(a: Int, b: Int) = a + b
47
Keyword
Name Parameters Returntype
Body
METHODS
def add(a: Int, b: Int) = a + b
48
METHODS
def add(a: Int, b: Int) = a + b
add(2, 2) // 4
49
NAMEDANDDEFAULTARGUMENTS
def resize(width: Int, height: Int, notify: Boolean = true)

50
NAMEDANDDEFAULTARGUMENTS
def resize(width: Int, height: Int, notify: Boolean = true)



resize(100, 200)

51
NAMEDANDDEFAULTARGUMENTS
def resize(width: Int, height: Int, notify: Boolean = true)



resize(100, 200)

resize(100, 200, false)
52
NAMEDANDDEFAULTARGUMENTS
def resize(width: Int, height: Int, notify: Boolean = true)



resize(100, 200)

resize(100, 200, false)
resize(100, 200, notify = false)
53
NAMEDANDDEFAULTARGUMENTS
def resize(width: Int, height: Int, notify: Boolean = true)



resize(100, 200)

resize(100, 200, false)
resize(100, 200, notify = false)
resize(width = 100, height = 200)

resize(height = 200, width = 100)

resize(height = 200, width = 100, notify = false)
54
FUNCTIONS
55
FUNCTIONS
• Functions are first class citizens
• Create, assign, apply
56
METHODS
def add(a: Int, b: Int) = a + b
57
FUNCTIONS
(a: Int, b: Int) => a + b
58
FUNCTIONS
val add = (a: Int, b: Int) => a + b
59
FUNCTIONS
val add = (a: Int, b: Int) => a + b // (Int, Int) => Int
60
FUNCTIONS
val add = new Function2[Int, Int, Int] {
def apply(a: Int, b: Int): Int = a + b
}
61
FUNCTIONS
val add = (a: Int, b: Int) => a + b
add.apply(2, 2) // 4
62
FUNCTIONS
val add = (a: Int, b: Int) => a + b
add(2, 2) // 4
63
PARTIALFUNCTIONS
64
PARTIALFUNCTIONS
val isEven: PartialFunction[Int, String] = {
case x if x % 2 == 0 => x + " is even"
}
isEven(2) // "2 is even"
isEven(3) // throws MatchError
65
PARTIALFUNCTIONS
val isEven: PartialFunction[Int, String] = {
case x if x % 2 == 0 => x + " is even"
}
isEven.isDefinedAt(2) // true
isEven.isDefinedAt(3) // false
66
EXERCISE2
67
COLLECTIONSI
COLLECTIONS
val numbers = List(17, 42, 314)



69
COLLECTIONS
val numbers = List(17, 42, 314)



numbers(1) // 42

70
COLLECTIONS
val numbers = List(17, 42, 314)



numbers(1) // 42

val clicks = Map("vg" -> 231232, "aftenposten" -> 158742)

71
COLLECTIONS
val numbers = List(17, 42, 314)



numbers(1) // 42

val clicks = Map("vg" -> 231232, "aftenposten" -> 158742)



clicks("vg") // 231232
72
COLLECTIONS
val numbers = List.apply(17, 42, 314)



numbers.apply(1) // 42

val clicks = Map.apply("vg" -> 231232, "aftenposten" -> 158742)



clicks.apply("vg”) // 231232
73
COLLECTIONS
• Collections ≈ partial functions
74
COLLECTIONOPERATIONS
75
CONSTRUCTION


val numbers = List(42, 314)



val moreNumbers = 17 :: numbers // List(17, 42, 314)
val fromScratch = 1 :: 2 :: 3 :: Nil // List(1, 2, 3)
76
HEADSANDTAILS


val numbers = List(17, 42, 314)



val first = numbers.head // 17
val rest = numbers.tail // List(42, 314)
77
SIZE


val names: List[String] = ...



val numberOfNames = names.size

78
SIZE


val names: List[String] = ...



val numberOfNames = names.length

79
EMPTINESS


val names: List[String] = ...



val empty = names.length == 0

80
EMPTINESS


val names: List[String] = ...



val empty = names.isEmpty

81
EMPTINESS


val names: List[String] = ...



val notEmpty = !names.isEmpty

82
EMPTINESS


val names: List[String] = ...



val notEmpty = names.nonEmpty

83
FILTERING


val names: List[String] = ...



val skywalkers = names.filter( ??? )

84
FILTERING


val names: List[String] = ...



val skywalkers = names.filter( ??? )

85
(A) => Boolean
FILTERING


val names: List[String] = ...



val skywalkers = names.filter( ??? )

86
(String) => Boolean
FILTERING


val names: List[String] = ...



val skywalkers = names.filter((name: String) => name.contains("Skywalker"))

87
FILTERING


val names: List[String] = ...



val skywalkers = names.filter(name => name.contains("Skywalker"))

88
FILTERING


val names: List[String] = ...



val skywalkers = names.filter(_.contains(“Skywalker"))

89
FILTERING


val names: List[String] = ...

val nameFilter: (String) => Boolean = _.contains(“Skywalker")

val skywalkers = names.filter(nameFilter)

90
FILTERING


val names: List[String] = ...

val nameFilter = (name: String) => name.contains("Skywalker")

val skywalkers = names.filter(nameFilter)

91
FILTERING


val names: List[String] = ...

def nameFilter(name: String) = name.contains("Skywalker")

val skywalkers = names.filter(nameFilter)

92
ITERATING


val names: List[String] = ...

names.foreach(name => println(name))

93
ITERATING


val names: List[String] = ...

names.foreach(println(_))

94
ITERATING


val names: List[String] = ...

names.foreach(println)

95
TRANSFORMING


val people: List[Person] = ...



val names = people.map( ??? )

96
(A) => B
TRANSFORMING


val people: List[Person] = ...



val names = people.map( ??? )

97
(Person) => B
TRANSFORMING


val people: List[Person] = ...



val names = people.map(person => person.name)

98
(Person) => String
TRANSFORMING


val people: List[Person] = ...



val names = people.map(person => person.name) // List[String]
99
(Person) => String
TRANSFORMING


val people: List[Person] = ...



val names = people.map(_.name) // List[String]
100
(Person) => String
SORTING


val names: List[String] = ...



val sortedNames = names.sorted

101
SORTING


val names: List[String] = ...



val reverseNames = names.sorted.reverse

102
SORTING


val names: List[String] = ...



val reverseNames = names.sorted(Ordering[String].reverse)
103
SORTING


val people: List[Person] = ...



val sortedByName = people.???

104
SORTING


val people: List[Person] = ...



val sortedByName = people.sortBy( ??? )

105
(A) => B // Ordering[B]
SORTING


val people: List[Person] = ...



val sortedByName = people.sortBy( ??? )

106
(Person) => B // Ordering[B]
SORTING


val people: List[Person] = ...



val sortedByName = people.sortBy(person => person.name)

107
(Person) => String // Ordering[String]
SORTING


val people: List[Person] = ...



val sortedByName = people.sortBy(_.name)

108
(Person) => String // Ordering[String]
SORTING


val people: List[Person] = ...



val reverseByName = people.sortBy(_.name)(Ordering[String].reverse)

109
AGGREGATING


val numbers = List(17, 42, 314)

val max = numbers.max // 314
val sum = numbers.sum // 373
110
COLLECTIONTYPES
• Sequences, Sets and Maps
• Immutable vs Mutable
• Performance
111
SEQUENCES
Immutable
• List
• Vector
• Queue
• Range
112
Mutable
• LinkedList
• ArrayBuffer / ListBuffer
• Queue
• ArrayStack
PERFORMANCE
113
Head Tail Length Lookup Update Prepend Append Insert
List 1 1 n n n 1 n -
Vector 1 * 1 * 1 * ? 1 * 1 * 1 * 1 * -
Queue 1 ** 1 ** n n n 1 1 -
ArrayBuffer 1 n 1 1 1 n 1 ** n
* = effectively ** = amortized
SETSANDMAPS
• HashSet
• TreeSet
• LinkedHashSet
• BitSet
114
• HashMap
• TreeMap
• LinkedHashMap
• IntMap / LongMap
COLLECTIONSAPI
• isEmpty, nonEmpty, size
• indexOf, contains, exists, forall, count
• head, tail, headOption, tailOption
• take, drop, takeWhile, dropWhile
• map, flatMap, foreach
• filter, filterNot, collect, partition
• sum, product, min, max, aggregate, fold
• reverse, sorted, sortBy, groupBy
• splitAt, grouped, sliding, combinations
115
MOREREADING
• http://docs.scala-lang.org/overviews/collections/overview.html
116
EXERCISE3
117
SCALAWORKSHOPKRAKOW
OCTOBER2017-DAY2
Fredrik Vraalsen - Data Platform
118
AGENDADAY2-FP
• Classes and Pattern Matching
• Collections II
• Laziness, implicits, DSLs
• QA & Wrapup
119
CLASSESANDPATTERNMATCHING
CLASSES
• Classes
• Traits
• Tuples
• Option
• Pattern matching
121
CLASSES
class Person(val name: String, var age: Int)
122
CLASSES
class Person(val name: String, var age: Int) {
// properties
// methods
// initialisation
}

123
CLASSES
package com.schibsted.model
import some.package.{ClassA, ClassB}
import some.other.package._
class Person(val name: String, var age: Int) {
// properties
// methods
// initialisation
}

124
CLASSES
class Person(val name: String, var age: Int)
val me = new Person("Fredrik", 41)

me.name // "Fredrik"
me.age // 41
me.age = 42
125
SINGLETONOBJECTS
object Person {

val constant = 42



def encode(user: Person): Json = ...

def decode(json: Json): Person = ...

def find(userId: Long): Person = ...

}
126
CASECLASSES
• Auto-generated methods
• Companion Object
127
CASECLASSES
case class Person(name: String, age: Int)

128
CASECLASSES
case class Person(name: String, age: Int)
class Person(val name: String, val age: Int) {

def copy(name: String, age: Int): Person

override def toString(): String

// hashCode, equals, etc.
}

129
CASECLASSES
case class Person(name: String, age: Int)
class Person(val name: String, val age: Int) {

def copy(name: String, age: Int): Person

override def toString(): String

// hashCode, equals, etc.
}



object Person {

def apply(name: String, age: Int): Person

// more
}
130
CASECLASSES
case class Person(name: String, age: Int)



val me = Person("Fredrik", 41)
val olderMe = me.copy(age = 42)
131
CASECLASSES
case class Person(name: String, age: Int)



val me = Person("Fredrik", 41)
val olderMe = me.copy(age = 42)
class Person(val name: String, val age: Int) {

def copy(name: String = this.name, age: Int = this.age): Person

...
}

132
INHERITANCE
• Single inheritance
133
INHERITANCE
abstract class Shape(val color: String) {
def area: Double
}
class Circle(val radius: Double, color: String) extends Shape(color) {
def area = radius * radius * Math.PI
}
val c = new Circle(1.0, "blue")
c.color // "blue"
c.area // 3.14159...
134
TRAITS
• Let’s you mix code into your class
• “Interfaces on steroids”
• Methods, values (abstract or implemented)
135
TRAITS
trait Ordered[T] {

def compare(that: T): Int



}
136
TRAITS
trait Ordered[T] {

def compare(that: T): Int



def < (that: A): Boolean = (this compare that) < 0
def > (that: A): Boolean = (this compare that) > 0
def <= (that: A): Boolean = (this compare that) <= 0
def >= (that: A): Boolean = (this compare that) >= 0
}
137
TRAITS
case class Person(name: String, age: Int)
extends Ordered[Person] {

override def compare(that: Person) = this.age - that.age
}
138
TRAITS
case class Person(name: String, age: Int)
extends Ordered[Person] {

override def compare(that: Person) = this.age - that.age
}
val a = Person("Fredrik", 41)

val b = Person("Johannes", 8)



a.>(b) // true

139
TRAITS
case class Person(name: String, age: Int)
extends Ordered[Person] {

override def compare(that: Person) = this.age - that.age
}
val a = Person("Fredrik", 41)

val b = Person("Johannes", 8)



a > b // true

140
TRAITS
case class Person(name: String, age: Int)
extends Ordered[Person] {

override def compare(that: Person) = this.age compare that.age
}
val a = Person("Fredrik", 41)

val b = Person("Johannes", 8)



a > b // true

141
MULTIPLETRAITS
case class Person(name: String, age: Int)
extends Ordered[Person]

with Cloneable
with Serializable {

override def compare(that: Person) = this.age compare that.age
}
142
TUPLES
• Unnamed structs
• Individually typed elements
143
TUPLES
val t1 = Tuple2("Fredrik", 41) // (String, Int)
val t2 = ("Fredrik", 41) // (String, Int)
t2._1 // "Fredrik"
t2._2 // 41
144
ADTS
• Algebraic Data Types
145
ADTS
sealed trait Tree
case class Node(left: Tree, right: Tree) extends Tree
case class Leaf(value: Int) extends Tree
val tree = Node(Node(Leaf(1), Leaf(2)), Leaf(3))
146
1 2
3
ADTS
sealed trait Tree[T]
case class Node[T](left: Tree[T], right: Tree[T]) extends Tree[T]
case class Leaf[T](value: T) extends Tree[T]
val tree = Node(Node(Leaf('a'), Leaf('b')), Leaf('c'))
147
a b
c
ADTS
sealed trait Json
case class JObject(fields: Map[String, Json]) extends Json
case class JArray(arr: List[Json]) extends Json
case class JString(str: String) extends Json
case class JNumber(num: BigDecimal) extends Json
case class JBool(value: Boolean) extends Json
case object JNull extends Json
val json = JObject(Map(
"name" -> JString("Fredrik"),
"age" -> JNumber(41)
))
148
ADTS
sealed trait Json
case class JObject(fields: Map[String, Json]) extends Json
case class JArray(arr: List[Json]) extends Json
case class JString(str: String) extends Json
case class JNumber(num: BigDecimal) extends Json
case class JBool(value: Boolean) extends Json
case object JNull extends Json
object JObject {
def apply(fields: (String, Json)*) = new JObject(fields.toMap)
}
val json = JObject(
"name" -> JString("Fredrik"),
"age" -> JNumber(41)
)
149
OPTIONALS
• Can contain an optional value of some type
• Transformations etc. work on the "happy path"
• Avoid null errors
150
OPTIONALS
val employees: Map[Int, Person] = …
employees(314)
151
OPTIONALS
val employees: Map[Int, Person] = …
employees.get(314) // Option[Person]
152
OPTIONALS
val employees: Map[Int, Person] = …
employees.get(314)
.map(_.name) // Option[String]
153
OPTIONALS
val employees: Map[Int, Person] = …
employees.get(314)
.map(_.name)
.getOrElse("Not found") // String
154
PATTERNMATCHING
• Values
• Types
• Structures
155
MATCHINGVALUES
val value = …
val result = value match {

case 1 => "number"

case "two" => "string"

case false => "boolean"

case _ => "unknown"

}



156
MATCHINGTYPES
val json = …
def jsonStr(json: Json) = json match {
case s: JString => '"' + s.str + '"'
case n: JNumber => n.num.toString
case b: JBool => b.value.toString
case JNull => "null"
}
val s = jsonStr(json)
157
MATCHINGSTRUCTURES
val json = …
val jsonStr = json match {
case JString(str) => '"' + str + '"'
case JNumber(num) => num.toString
case JBool(value) => value.toString
case JNull => "null"
}
158
MATCHINGSTRUCTURES
val tuple = …
val second = tuple match {

case (a, b) => b

}
159
MATCHINGSTRUCTURES
val tuple = …
val second = tuple match {

case (_, b) => b

}
160
MATCHINGSTRUCTURES
val tuple = …
val second = tuple match {

case ("special", b) => s"special $b"

case (_, b) => b

}
161
MATCHINGSTRUCTURES
val optional = …
val value = optional match {

case Some(value) => value

case None => defaultValue

}
162
MATCHINGSTRUCTURES
val optional = …
val value = optional match {

case Some(value) => value

case None => defaultValue

}
// optional.getOrElse(defaultValue)
163
MATCHINGSTRUCTURES
val list = …
val first = list match {

case head :: tail => head

case Nil => "Not found"

}
164
MATCHINGSTRUCTURES
val list = …
val value = list match {

case List(_, b) => b

case List(a, _*) => a

case Nil => "Not found"

}



165
MATCHINGREGULAREXPRESSIONS
val email = "(.*)@(.*..*)".r



"fredrik@vraalsen.no" match {

case email(user, domain) => s"User: $user, domain: $domain"

case _ => "Invalid email"

}

166
EXERCISE4
167
COLLECTIONSII
SUBLISTS


val numbers: List[Int] = List(1, 2, 3, 4, 5)



val (before, after) = numbers.splitAt(3) // (List(1, 2, 3), List(4, 5))

169
PARTITIONING


val people: List[Person] = ...



val adults = people.filter(_.age >= 18)

val kids = people.filterNot(_.age >= 18)

170
PARTITIONING


val people: List[Person] = ...



val (adults, kids) = people.partition(_.age >= 18)

171
SUBLISTS


val numbers: List[Int] = List(1, 2, 3, 4, 5)



val grouped = numbers.grouped(2) // List(1, 2), List(3, 4), List(5)

172
SUBLISTS


val numbers: List[Int] = List(1, 2, 3, 4, 5)



val grouped = numbers.sliding(3)
// List(1, 2, 3), List(2, 3, 4), List(3, 4, 5)

173
COMBINING


val numbers: List[Int] = List(17, 42)

val chars: List[Char] = List('a', 'b')



val combined = numbers.zip(chars) // List((17, 'a'), (42, 'b'))

174
COMBINING


val chars: List[Char] = List('a', 'b')



val indexed = chars.zipWithIndex // List(('a', 0), ('b', 1))

175
NESTEDDATA
def getFiles(date: LocalDate): List[String]

def getEvents(filename: String): List[Json]

def getUserId(event: Json): String

176
NESTEDDATA
def getFiles(date: LocalDate): List[String]

def getEvents(filename: String): List[Json]

def getUserId(event: Json): String

val filenames = getFiles(LocalDate.of(2016, 10, 10))



177
NESTEDDATA
def getFiles(date: LocalDate): List[String]

def getEvents(filename: String): List[Json]

def getUserId(event: Json): String

val filenames = getFiles(LocalDate.of(2016, 10, 10))

val events = filenames.map(file => getEvents(file))



178
NESTEDDATA
def getFiles(date: LocalDate): List[String]

def getEvents(filename: String): List[Json]

def getUserId(event: Json): String



val filenames = getFiles(LocalDate.of(2016, 10, 10))

val events = filenames.map(file => getEvents(file)) // List[List[Json]]



179
NESTEDDATA
def getFiles(date: LocalDate): List[String]

def getEvents(filename: String): List[Json]

def getUserId(event: Json): String

val filenames = getFiles(LocalDate.of(2016, 10, 10))

val events = filenames.map(file => getEvents(file)).flatten // List[Json]
180
FLATMAP
def getFiles(date: LocalDate): List[String]

def getEvents(filename: String): List[Json]

def getUserId(event: Json): String

val filenames = getFiles(LocalDate.of(2016, 10, 10))

val events = filenames.flatMap(file => getEvents(file)) // List[Json]
181
FLATMAP
def getFiles(date: LocalDate): List[String]

def getEvents(filename: String): List[Json]

def getUserId(event: Json): String

val filenames = getFiles(LocalDate.of(2016, 10, 10))

val events = filenames.flatMap(file => getEvents(file)) // List[Json]
val userIds = events.map(event => getUserId(event)) // List[String]
182
FLATMAP
def getFiles(date: LocalDate): List[String]

def getEvents(filename: String): List[Json]

def getUserId(event: Json): String

val userIds = getFiles(LocalDate.of(2016, 10, 10))

.flatMap(file => getEvents(file)) // List[Json]
.map(event => getUserId(event)) // List[String]
183
FLATMAP
def getFiles(date: LocalDate): List[String]

def getEvents(filename: String): List[Json]

def getUserId(event: Json): String

val userIds = getFiles(LocalDate.of(2016, 10, 10))

.flatMap(getEvents(_)) // List[Json]
.map(getUserId(_)) // List[String]
184
FLATMAP
def getFiles(date: LocalDate): List[String]

def getEvents(filename: String): List[Json]

def getUserId(event: Json): String

val userIds = getFiles(LocalDate.of(2016, 10, 10))

.flatMap(getEvents) // List[Json]
.map(getUserId) // List[String]
185
MAP,FILTER&FLATMAP
• Reach into the container
• List, Option, Try, Future, …
• Take the happy path
186
FOR-COMPREHENSIONS
def getFiles(date: LocalDate): List[String]

def getEvents(filename: String): List[Json]

def getUserId(event: Json): String

val userIds = getFiles(LocalDate.of(2016, 10, 10))

.flatMap(file => getEvents(file)) // List[Json]
.map(event => getUserId(event)) // List[String]
187
FOR-COMPREHENSIONS
def getFiles(date: LocalDate): List[String]

def getEvents(filename: String): List[Json]

def getUserId(event: Json): String

val userIds = getFiles(LocalDate.of(2016, 10, 10))

.flatMap(file => getEvents(file)) // List[Json]
.map(event => getUserId(event)) // List[String]
val userIds = for {

file <- getFiles(LocalDate.of(2016, 10, 10))

event <- getEvents(file)

} yield getUserId(file, event)

188
COLLECT
List result = new ArrayList();
for (Element element : elements) {
if (element instanceof SpecialElement) {
SpecialElement se = (SpecialElement) se;
if (accept(se))
result.add(transform(se));
}
}
189
https://ochafik.com/blog/?p=393
COLLECT
List result = new ArrayList();
for (Element element : elements) {
if (element instanceof SpecialElement) {
SpecialElement se = (SpecialElement) element;
if (accept(se))
result.add(transform(se));
}
}
val result = elements.collect {
case se: SpecialElement if accept(se) => transform(se)
}
190
https://ochafik.com/blog/?p=393
ACCUMULATION
def sum(numbers: List[Int])

191
ACCUMULATION
def sum(numbers: List[Int]) = {

var sum = 0

for (i <- numbers) sum += i

sum

}

192
RECURSION
def sum(numbers: List[Int]): Int = numbers match {

case Nil => 0

case head :: tail => head + sum(tail)

}

193
RECURSION
@tailrec

def sum(numbers: List[Int]): Int = numbers match {

case Nil => 0

case head :: tail => head + sum(tail)

}



194
RECURSION
def sum(numbers: List[Int]) = {

@tailrec

def loop(acc: Int, numbers: List[Int]): Int = numbers match {

case Nil => acc

case head :: tail => loop(acc + head, tail)

}



loop(0, numbers)

}

195
RECURSION-THEGOTOOFFP
196
FOLD
197
FOLD
• Initial value
• Accumulator
• Operator
198
FOLD
def sum(numbers: List[Int]) = {

@tailrec

def loop(acc: Int, numbers: List[Int]): Int = numbers match {

case Nil => acc

case head :: tail => loop(acc + head, tail)

}



loop(0, numbers)

}

199
FOLD
def sum(numbers: List[Int]) = {

@tailrec

def loop(acc: Int, numbers: List[Int]): Int = numbers match {

case Nil => acc

case head :: tail => loop(acc + head, tail)

}



loop(0, numbers)

}



def sum(numbers: List[Int]) = numbers.foldLeft(0)((acc, n) => acc + n)
200
FOLD
def sum(numbers: List[Int]) = {

@tailrec

def loop(acc: Int, numbers: List[Int]): Int = numbers match {

case Nil => acc

case head :: tail => loop(acc + head, tail)

}



loop(0, numbers)

}



def sum(numbers: List[Int]) = numbers.foldLeft(0)(_ + _)
201
EXERCISE5
202
LAZINESS,IMPLICITSANDDSLS
LAZINESS
• Streams
• Iterators
• Views
204
STREAMS
• Lazy sequence
• Values computed on demand first time
• Can be accessed many times
205
ITERATORS
• Iterate over elements one by one
• Use once
206
VIEWS
• Lazy transformations
• map, filter, etc.
• Use once
207
IMPLICITS
• Values / parameters / methods automagically inserted by compiler
208
IMPLICITS


val people: List[Person] = ...



val sortByName = people.sortBy(_.name)

209
IMPLICITS


val people: List[Person] = ...



val sortByName = people.sortBy(_.name)
def sortBy[B](f: A => B): List[B]
210
IMPLICITS


val people: List[Person] = ...



val sortByName = people.sortBy(_.name)
def sortBy[B](f: A => B)(implicit ord: scala.math.Ordering[B]): List[B]
211
IMPLICITS


val people: List[Person] = ...



val sortByName = people.sortBy(_.name)(Ordering[String])
def sortBy[B](f: A => B)(implicit ord: scala.math.Ordering[B]): List[B]
212
IMPLICITS
val clicks = Map("vg" -> 231232, "aftenposten" -> 158742)

213
IMPLICITS
val clicks = Map("vg" -> 231232, "aftenposten" -> 158742)

val clicks = Map(Tuple2(“vg”, 231232), Tuple2("aftenposten", 158742))

214
IMPLICITS
val clicks = Map("vg" -> 231232, "aftenposten" -> 158742)

val clicks = Map(Tuple2(“vg”, 231232), Tuple2("aftenposten", 158742))

implicit class ArrowAssoc[A](self: A) extends AnyVal {
def -> [B](b: B): Tuple2[A, B] = Tuple2(self, b)
}
215
DSLS
• Domain Specific Languages
• Decorators
• "Pimp my library"
• Jackson
• Kafka Streams
216
REPLACINGJSON4S
def providerId(event: JValue) = event  "provider"  "@id"
217
JACKSON
def providerId(event: JsonNode) = event.get("provider").get("@id")
218
JACKSON
implicit class RichJsonNode(node: JsonNode) {
def (fieldName: String): JsonNode =
Option(node get fieldName) getOrElse NullNode.instance
}
def providerId(event: JsonNode) = event  "provider"  "@id"
219
JACKSON
implicit class RichJsonNode(node: JsonNode) {
def (fieldName: String): JsonNode =
node path fieldName
}
def providerId(event: JsonNode) = event  "provider"  "@id"
220
221
KAFKASTREAMS
val events = builder.stream(strings, json, sourceTopic)
val transformed = events.filter( … ).map( … )
transformed.to(strings, json, resultTopic)
222
KAFKASTREAMS
val events = builder.stream(strings, json, sourceTopic)
val transformed = events.filter( … ).map( … )
transformed.to(resultTopic) // ???
223
KAFKASTREAMS
class KStream[K, V] {
def to(keySerde: Serde[K], valSerde: Serde[V], topic: TopicName)
}
224
KAFKASTREAMS
object KafkaStreamsDSL {
implicit class RichKStream[K, V](stream: KStream[K, V]) {
def to(topic: TopicName)
}
}
225
KAFKASTREAMS
object KafkaStreamsDSL {
implicit class RichKStream[K, V](stream: KStream[K, V]) {
def to(topic: TopicName): Unit =
stream.to(keySerde, valSerde, topic)
}
}
226
KAFKASTREAMS
object KafkaStreamsDSL {
implicit class RichKStream[K, V](stream: KStream[K, V]) {
def to(topic: TopicName)
(implicit keySerde: Serde[K], valSerde: Serde[V]): Unit =
stream.to(keySerde, valSerde, topic)
}
}
227
KAFKASTREAMS
val events = builder.stream(strings, json, sourceTopic)
val transformed = events.filter( … ).map( … )
transformed.to(resultTopic) // ???
228
KAFKASTREAMS
val events = builder.stream(strings, json, sourceTopic)
val transformed = events.filter( … ).map( … )
import KafkaStreamsDSL._
implicit val strings = Serdes.string()
implicit val json = new JsonNodeSerde
transformed.to(resultTopic)
229
KAFKASTREAMS
val events = builder.stream(strings, json, sourceTopic)
val transformed = events.filter( … ).map( … )
import KafkaStreamsDSL._
implicit val strings = Serdes.string()
implicit val json = new JsonNodeSerde
transformed.~>(resultTopic)
230
KAFKASTREAMS
val events = builder.stream(strings, json, sourceTopic)
val transformed = events.filter( … ).map( … )
import KafkaStreamsDSL._
implicit val strings = Serdes.string()
implicit val json = new JsonNodeSerde
transformed ~> resultTopic
231
EVOLUTION
232
EVOLUTION
def extractUsers(iam: AmazonIdentityManagementClient, marker: String):
Seq[JsObject] = {
val result = iam.listUsers(new ListUsersRequest().withMarker(marker))
val batch = result.getUsers().asScala.map(userJson(iam, _))
val next: Seq[JsObject] =
if (result.getMarker() != null) {
extractUsers(iam, result.getMarker())
} else {
Seq[JsObject]()
}
batch ++ next
}
233
EVOLUTION
def extractGroups(iam: AmazonIdentityManagementClient, marker: String):
Seq[JsObject] = {
val result = iam.listGroups(new ListGroupsRequest().withMarker(marker))
val batch = result.getGroups().asScala.map(groupJson(_))
val next: Seq[JsObject] =
if (result.getMarker() != null) {
extractGroups(iam, result.getMarker())
} else {
Seq[JsObject]()
}
batch ++ next
}
234
EVOLUTION
def extract[A](fetch: String => A,
convert: A => Seq[JsObject],
isDone: A => Boolean,
getMarker: A => String): Seq[JsObject] = {
def loop(marker: String): Seq[JsObject] = {
val result = fetch(marker)
val batch = convert(result)
if (isDone(result)) batch else batch ++ loop(getMarker(result))
}
loop(null)
}
235
EVOLUTION
def extractUsers(iam: AmazonIdentityManagement) =
extract[ListUsersResult](
marker => iam.listUsers(new ListUsersRequest().withMarker(marker)),
result => result.getUsers.asScala.map(userJson(iam, _)),
_.isTruncated,
_.getMarker
)
def extractGroups(iam: AmazonIdentityManagement) =
extract[ListGroupsResult](
marker => iam.listGroups(new ListGroupsRequest().withMarker(marker)),
result => result.getGroups.asScala.map(groupJson),
_.isTruncated,
_.getMarker
)
236
EVOLUTION
type AwsListResult = {
def isTruncated(): java.lang.Boolean
def getMarker(): String
}
237
EVOLUTION
def extract[A](fetch: String => A,
convert: A => Seq[JsObject],
isDone: A => Boolean,
getMarker: A => String): Seq[JsObject] = {
def loop(marker: String): Seq[JsObject] = {
val result = fetch(marker)
val batch = convert(result)
if (isDone(result)) batch else batch ++ loop(getMarker(result))
}
loop(null)
}
238
EVOLUTION
def extract[A <: AwsListResult](fetch: String => A,
convert: A => Seq[JsObject]): Seq[JsObject] = {
def loop(marker: String): Seq[JsObject] = {
val result = fetch(marker)
val batch = convert(result)
if (result.isTruncated()) batch ++ loop(result.getMarker()) else batch
}
loop(null)
}
239
EVOLUTION
def extractUsers(iam: AmazonIdentityManagement) =
extract(
marker => iam.listUsers(new ListUsersRequest().withMarker(marker)),
result => result.getUsers.asScala.map(userJson(iam, _))
)
def extractGroups(iam: AmazonIdentityManagement) =
extract(
marker => iam.listGroups(new ListGroupsRequest().withMarker(marker)),
result => result.getGroups.asScala.map(groupJson)
)
240
EVOLUTION
def extract[A <: AwsListResult](fetch: String => A,
convert: A => Seq[JsObject]): Seq[JsObject] = {
def loop(marker: String): Seq[JsObject] = {
val result = fetch(marker)
val batch = convert(result)
if (result.isTruncated()) batch ++ loop(result.getMarker()) else batch
}
loop(null)
}
241
EVOLUTION
def extract[A <: AwsListResult, B](fetch: String => A)
(extract: A => java.util.List[B])
(implicit toJson: B => JsObject): Seq[JsObject] =
def loop(marker: String): Seq[JsObject] = {
val result = fetch(marker)
val batch = extract(result).asScala.map(toJson)
if (result.isTruncated()) batch ++ loop(result.getMarker()) else batch
}
loop(null)
}
242
EVOLUTION
def extractUsers() = extract(
marker => iam.listUsers(new ListUsersRequest().withMarker(marker))
)(_.getUsers)
def extractGroups() = extract(
marker => iam.listGroups(new ListGroupsRequest().withMarker(marker))
)(_.getGroups)
243
EVOLUTION
def extractUsers() = extract(users)(_.getUsers)
def extractGroups() = extract(groups)(_.getGroups)
def users(marker: String) =
iam.listUsers(new ListUsersRequest().withMarker(marker))
def groups(marker: String) =
iam.listGroups(new ListGroupsRequest().withMarker(marker))
244
ASYNC
245
ASYNC
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): List[Json]

246
ASYNC
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): List[Json]



val content = getFile(LocalDate.of(2016, 10, 10))
val events = ???
247
ASYNC
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): List[Json]



val content = getFile(LocalDate.of(2016, 10, 10))

content.onComplete {


}

248
ASYNC
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): List[Json]



val content = getFile(LocalDate.of(2016, 10, 10))

content.onComplete { res =>
res match {

case Success(json) => doSomething(json)

case Failure(ex) => fallbackLogic(ex)
}

}

249
ASYNC
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): List[Json]



val content = getFile(LocalDate.of(2016, 10, 10))

content.onComplete {

case Success(json) => doSomething(json)

case Failure(ex) => fallbackLogic(ex)

}

250
ASYNC
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): List[Json]



val content = getFile(LocalDate.of(2016, 10, 10))

content.onComplete {

case Success(json) => doSomething(json)

case Failure(ex) => fallbackLogic(ex)

}

val events = ???
251
MAP
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): List[Json]



val content = getFile(LocalDate.of(2016, 10, 10))
val events = content.map(json => parseEvents(json))
252
MAP
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): List[Json]



val content = getFile(LocalDate.of(2016, 10, 10))
val events = content.map(json => parseEvents(json)) // Future[List[Json]]
253
MAP
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): List[Json]



val content = getFile(LocalDate.of(2016, 10, 10))
val events = content.map(json => parseEvents(json)) // Future[List[Json]]
Await.result(events, Duration.Inf)
254
MAP
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): Future[List[Json]]



val content = getFile(LocalDate.of(2016, 10, 10))
val events = content.map(json => parseEvents(json))
// Future[Future[List[Json]]]
255
FLATMAP
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): Future[List[Json]]



val content = getFile(LocalDate.of(2016, 10, 10))
val events = content.flatMap(json => parseEvents(json)) // Future[List[Json]]
256
FOR-COMPREHENSIONS
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): Future[List[Json]]



val content = getFile(LocalDate.of(2016, 10, 10))
val events = content.flatMap(json => parseEvents(json))
257
FOR-COMPREHENSIONS
def getFile(date: LocalDate): Future[String]

def parseEvents(json: String): Future[List[Json]]



val content = getFile(LocalDate.of(2016, 10, 10))
val events = content.flatMap(json => parseEvents(json))


val events = for {

json <- getFile(LocalDate.of(2016, 10, 10))

events <- parseEvents(json)

} yield events



258
PARALLELLISM
• Futures for-comprehension (flatMap) vs zip/zipWith vs sequence
• traverse (parallel map)
259
WRAPUPANDQA
IMPERATIVEVSFUNCTIONAL
• Imperative : Change state
• Functional Programming (FP) : Compute new values
261
OBJECT-ORIENTEDVSFUNCTIONAL
• OO: Objects = state + behavior
• FP: Data structures + functions
262
OO+FP
• Scala = Hybrid FP + OO
• Leans towards FP “in the small”
• OO for “programming in the large”
263
DON’TBECUTE
• Optimize for readability
• Find the Scala level that works for your team
• The language grows with your understanding
• of Scala, and of FP
264
QA
• Questions?
• Anything you want to dive deeper into?
265
THANKYOU!
@fredriv / fredrik.vraalsen@schibsted.com

Scala intro workshop