SlideShare a Scribd company logo
1 of 92
Download to read offline
The Terror-Free Guide to
Introducing Functional Scala at Work
(without dying in the process)
JORGE VÁSQUEZ
SCALA DEVELOPER
We are hiring Scala Developers!
https://scalac.io/careers/
Agenda
● Motivation
● ZIO Prelude Overview
● How ZIO Prelude can help us
Motivation:
Boilerplate
Example 1
Let's suppose we have some cache stats data, organized
by date and application. This data comes from two
sources, and we need to combine them into one, by
summing counters when collisions exist.
Example 1
Solution 1
final case class CacheStats(
entryCount: Option[Int],
memorySize: Option[Long],
hits: Option[Long],
misses: Option[Long],
loads: Option[Long],
evictions: Option[Long]
)
object CacheStats {
def make(
entryCount: Option[Int],
memorySize: Option[Long],
hits: Option[Long],
misses: Option[Long],
loads: Option[Long],
evictions: Option[Long]
): CacheStats = CacheStats(entryCount, memorySize, hits, misses, loads, evictions)
}
Solution 1
pprint.pprintln(stats1 ++ stats2)
/*
Map(
2020-01-18 -> Map(
"App X" -> CacheStats(None, Some(5000L), Some(2000L),
Some(500L), Some(5000L), Some(2500L)),
"App Y" -> CacheStats(Some(800), None, Some(3100L), None,
Some(7890L), Some(1513L)),
"App Z" -> CacheStats(None, Some(678L), None, None, Some(800L),
None)
),
2020-01-19 -> Map(
"App A" -> CacheStats(None, None, Some(4098L), None,
Some(5418L), None),
"App B" -> CacheStats(Some(1567), None, Some(4098L),
Some(1000L), Some(5418L), Some(3000L)),
"App C" -> CacheStats(None, None, Some(500L), Some(467L),
Some(800L), None)
),
2020-03-05 -> Map(
"App A" -> CacheStats(Some(4378), None, Some(3210L),
Some(1000L), None, None),
"App Y" -> CacheStats(None, Some(1345L), Some(9032L),
Some(123L), None, None)
),
2020-04-10 -> Map("App X" -> CacheStats(None, None, Some(432L),
None, Some(2541L), None))
)
*/
val stats1: Map[LocalDate, Map[String, CacheStats]] = Map(
LocalDate.of(2020, 1, 18) -> Map(
"App X" -> CacheStats.make(Some(1000), Some(5000), Some(2000), Some(500), Some(5000), Some(2500)),
"App Y" -> CacheStats.make(None, Some(3500), Some(3100), None, Some(7890), Some(1513)),
"App Z" -> CacheStats.make(None, None, Some(500), None, Some(800), None)
),
LocalDate.of(2020, 1, 19) -> Map(
"App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None),
"App B" -> CacheStats.make(Some(1567), Some(2854), Some(4098), Some(1000), Some(5418), Some(3000)),
"App C" -> CacheStats.make(None, None, Some(500), None, Some(800), None)
),
LocalDate.of(2020, 3, 5) -> Map(
"App A" -> CacheStats.make(Some(4378), Some(1000), Some(3210), Some(1000), None, None),
"App Y" -> CacheStats.make(None, None, Some(9032), Some(123), None, None)
),
LocalDate.of(2020, 4, 10) -> Map(
"App X" -> CacheStats.make(Some(1879), None, Some(432), None, Some(2541), None)
)
)
val stats2: Map[LocalDate, Map[String, CacheStats]] = Map(
LocalDate.of(2020, 1, 18) -> Map(
"App X" -> CacheStats.make(None, Some(5000), Some(2000), Some(500), Some(5000), Some(2500)),
"App Y" -> CacheStats.make(Some(800), None, Some(3100), None, Some(7890), Some(1513)),
"App Z" -> CacheStats.make(None, Some(678), None, None, Some(800), None)
),
LocalDate.of(2020, 1, 19) -> Map(
"App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None),
"App B" -> CacheStats.make(Some(1567), None, Some(4098), Some(1000), Some(5418), Some(3000)),
"App C" -> CacheStats.make(None, None, Some(500), Some(467), Some(800), None)
),
LocalDate.of(2020, 3, 5) -> Map(
"App A" -> CacheStats.make(Some(4378), None, Some(3210), Some(1000), None, None),
"App Y" -> CacheStats.make(None, Some(1345), Some(9032), Some(123), None, None)
),
LocalDate.of(2020, 4, 10) -> Map(
"App X" -> CacheStats.make(None, None, Some(432), None, Some(2541), None)
)
)
Solution 1
Problems with Solution 1
Solution 2
final case class CacheStats(
entryCount: Option[Int],
memorySize: Option[Long],
hits: Option[Long],
misses: Option[Long],
loads: Option[Long],
evictions: Option[Long]
) { self =>
def combine(that: CacheStats): CacheStats = {
def combineCounters[A: Numeric](left: Option[A], right: Option[A]): Option[A] =
(left, right) match {
case (Some(l), Some(r)) => Some(implicitly[Numeric[A]].plus(l, r))
case (Some(l), None) => Some(l)
case (None, Some(r)) => Some(r)
case (None, None) => None
}
CacheStats(
combineCounters(self.entryCount, that.entryCount),
combineCounters(self.memorySize, that.memorySize),
combineCounters(self.hits, that.hits),
combineCounters(self.misses, that.misses),
combineCounters(self.loads, that.loads),
combineCounters(self.evictions, that.evictions)
)
}
}
Solution 2
def combine(
left: Map[LocalDate, Map[String, CacheStats]],
right: Map[LocalDate, Map[String, CacheStats]]
): Map[LocalDate, Map[String, CacheStats]] = {
(left.keySet ++ right.keySet).map { date =>
val newStatsByApp = (left.get(date), right.get(date)) match {
case (Some(v1), None) => v1
case (None, Some(v2)) => v2
case (Some(v1), Some(v2)) =>
(v1.keySet ++ v2.keySet).map { location =>
val newStats = (v1.get(location), v2.get(location)) match {
case (Some(s1), None) => s1
case (None, Some(s2)) => s2
case (Some(s1), Some(s2)) => s1 combine s2
case (None, None) => throw new Error("Unexpected scenario")
}
location -> newStats
}.toMap
case (None, None) => throw new Error("Unexpected scenario")
}
date -> newStatsByApp
}
}.toMap
Solution 2
pprint.pprintln(combine(stats1, stats2))
/*
Map(
2020-01-18 -> Map(
"App X" -> CacheStats(Some(1000), Some(10000L), Some(4000L),
Some(1000L), Some(10000L), Some(5000L)),
"App Y" -> CacheStats(Some(800), Some(3500L), Some(6200L), None,
Some(15780L), Some(3026L)),
"App Z" -> CacheStats(None, Some(678L), Some(500L), None, Some(1600L),
None)
),
2020-01-19 -> Map(
"App A" -> CacheStats(None, None, Some(8196L), None, Some(10836L),
None),
"App B" -> CacheStats(Some(3134), Some(2854L), Some(8196L),
Some(2000L), Some(10836L), Some(6000L)),
"App C" -> CacheStats(None, None, Some(1000L), Some(467L), Some(1600L),
None)
),
2020-03-05 -> Map(
"App A" -> CacheStats(Some(8756), Some(1000L), Some(6420L),
Some(2000L), None, None),
"App Y" -> CacheStats(None, Some(1345L), Some(18064L), Some(246L),
None, None)
),
2020-04-10 -> Map("App X" -> CacheStats(Some(1879), None, Some(864L),
None, Some(5082L), None))
)
*/
val stats1: Map[LocalDate, Map[String, CacheStats]] = Map(
LocalDate.of(2020, 1, 18) -> Map(
"App X" -> CacheStats.make(Some(1000), Some(5000), Some(2000), Some(500), Some(5000), Some(2500)),
"App Y" -> CacheStats.make(None, Some(3500), Some(3100), None, Some(7890), Some(1513)),
"App Z" -> CacheStats.make(None, None, Some(500), None, Some(800), None)
),
LocalDate.of(2020, 1, 19) -> Map(
"App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None),
"App B" -> CacheStats.make(Some(1567), Some(2854), Some(4098), Some(1000), Some(5418), Some(3000)),
"App C" -> CacheStats.make(None, None, Some(500), None, Some(800), None)
),
LocalDate.of(2020, 3, 5) -> Map(
"App A" -> CacheStats.make(Some(4378), Some(1000), Some(3210), Some(1000), None, None),
"App Y" -> CacheStats.make(None, None, Some(9032), Some(123), None, None)
),
LocalDate.of(2020, 4, 10) -> Map(
"App X" -> CacheStats.make(Some(1879), None, Some(432), None, Some(2541), None)
)
)
val stats2: Map[LocalDate, Map[String, CacheStats]] = Map(
LocalDate.of(2020, 1, 18) -> Map(
"App X" -> CacheStats.make(None, Some(5000), Some(2000), Some(500), Some(5000), Some(2500)),
"App Y" -> CacheStats.make(Some(800), None, Some(3100), None, Some(7890), Some(1513)),
"App Z" -> CacheStats.make(None, Some(678), None, None, Some(800), None)
),
LocalDate.of(2020, 1, 19) -> Map(
"App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None),
"App B" -> CacheStats.make(Some(1567), None, Some(4098), Some(1000), Some(5418), Some(3000)),
"App C" -> CacheStats.make(None, None, Some(500), Some(467), Some(800), None)
),
LocalDate.of(2020, 3, 5) -> Map(
"App A" -> CacheStats.make(Some(4378), None, Some(3210), Some(1000), None, None),
"App Y" -> CacheStats.make(None, Some(1345), Some(9032), Some(123), None, None)
),
LocalDate.of(2020, 4, 10) -> Map(
"App X" -> CacheStats.make(None, None, Some(432), None, Some(2541), None)
)
)
Example 2
Let's suppose we have a List of cache stats data, and we
want to sum all stats.
Solution
final case class CacheStats(
entryCount: Option[Int],
memorySize: Option[Long],
hits: Option[Long],
misses: Option[Long],
loads: Option[Long],
evictions: Option[Long]
) { self =>
def combine(that: CacheStats): CacheStats = {
def combineCounters[A: Numeric](left: Option[A], right: Option[A]): Option[A] =
(left, right) match {
case (Some(l), Some(r)) => Some(implicitly[Numeric[A]].plus(l, r))
case (Some(l), None) => Some(l)
case (None, Some(r)) => Some(r)
case (None, None) => None
}
CacheStats(
combineCounters(self.entryCount, that.entryCount),
combineCounters(self.memorySize, that.memorySize),
combineCounters(self.hits, that.hits),
combineCounters(self.misses, that.misses),
combineCounters(self.loads, that.loads),
combineCounters(self.evictions, that.evictions)
)
}
}
Solution
def sum(cacheStats: List[CacheStats]): CacheStats =
cacheStats.foldRight(CacheStats.make(None, None, None, None, None, None))(_ combine _)
def main(args: Array[String]): Unit = {
val cacheStats1 = List(
CacheStats.make(Some(1000), Some(5000), Some(2000), Some(500), Some(5000), Some(2500)),
CacheStats.make(None, Some(3500), Some(3100), None, Some(7890), Some(1513)),
CacheStats.make(None, None, Some(500), None, Some(800), None)
)
val cacheStats2 = List.empty[CacheStats]
println(sum(cacheStats1)) // CacheStats(Some(1000), Some(8500L), Some(5600L), Some(500L), Some(13690L), Some(4013L))
println(sum(cacheStats2)) // CacheStats(None, None, None, None, None, None)
}
Example 3
Let's suppose we have several Options and we want to
combine them into an Option of a Tuple
Solution
val option1 = Option(1)
val option2 = Option(2)
val option3 = Option(3)
val option4 = Option(4)
val option5 = Option(5)
val option6 = Option(6)
val option7 = Option(7)
val option8 = Option(8)
val option9 = Option(9)
val option10 = Option(10)
println {
option1
.zip(option2)
.zip(option3)
.zip(option4)
.zip(option5)
.zip(option6)
.zip(option7)
.zip(option8)
.zip(option9)
.zip(option10)
.headOption
.map {
case (((((((((a, b), c), d), e), f), g), h), i), j) => (a, b, c, d, e, f, g, h, i, j)
}
}
// Some(Tuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
Example 4
Let's suppose we request some Person data from three
different servers, and we want to return the first successful
response, or a failure if all the requests fail
Solution
import com.twitter.util.{ Return, Throw, Try }
final case class Person(
firstName: String, lastName: String, country: String, state: String, age: Int
)
def getPerson(url: String): Try[Person] =
if (url.contains("1") || url.contains("2")) Throw(new Exception("Server error"))
else Return(Person("Ana", "Perez", "Bolivia", "La Paz", 30))
lazy val response1 = getPerson("http://server1.com/info")
lazy val response2 = getPerson("http://server2.com/info")
lazy val response3 = getPerson("http://server3.com/info")
val response: Try[Person] =
response1.rescue {
case _ =>
response2.rescue {
case _ => response3
}
}
println(response) // Return(Person(Ana,Perez,Bolivia,La Paz,30))
Solution
Example 5
Obtain the following information from a Binary Tree of Integers:
● The minimum value
● The maximum value
● The number of elements
● The number of elements greater than 5
● Does it contain the number 20?
● Does it contain negative numbers?
● Are all elements positive numbers?
● What’s the first element greater than 5?
● Is the tree empty?
● Is the tree non empty?
● What’s the sum of the elements?
● What’s the product of the elements?
● What’s the reversed tree?
Solution
sealed trait BinaryTree[+A]
object BinaryTree {
private final case class Leaf[A](value: A) extends BinaryTree[A]
private final case class Branch[A](left: BinaryTree[A], right: BinaryTree[A]) extends BinaryTree[A]
def leaf[A](value: A): BinaryTree[A] = BinaryTree.Leaf(value)
def branch[A](left: BinaryTree[A], right: BinaryTree[A]): BinaryTree[A] = BinaryTree.Branch(left, right)
}
Solution
sealed trait BinaryTree[+A] { self =>
import BinaryTree._
def contains[A1 >: A](a: A1): Boolean = self.count(_ == a) > 0
def count(f: A => Boolean): Int = self match {
case Leaf(a) => if (f(a)) 1 else 0
case Branch(l, r) => l.count(f) + r.count(f)
}
def exists(f: A => Boolean): Boolean = self.count(f) > 0
def find(f: A => Boolean): Option[A] = self match {
case Leaf(a) => Some(a).filter(f)
case Branch(l, r) => l.find(f).orElse(r.find(f))
}
def fold[A1 >: A](f: (A1, A1) => A1): A1 = self match {
case Leaf(a) => a
case Branch(left, right) => f(left.fold(f), right.fold(f))
}
def forall(f: A => Boolean): Boolean = self.count(f) == self.size
def isEmpty: Boolean = self.size == 0
...
}
Solution
sealed trait BinaryTree[+A] { self =>
import BinaryTree._
...
def map[B](f: A => B): BinaryTree[B] = self match {
case Leaf(a) => Leaf(f(a))
case Branch(left, right) => Branch(left.map(f), right.map(f))
}
def max[A1 >: A](implicit ordering: Ordering[A1]): A1 = self.maxBy(identity[A1])
def maxBy[B](f: A => B)(implicit ordering: Ordering[B]): A = self.fold(Ordering.by(f).max)
def min[A1 >: A](implicit ordering: Ordering[A1]): A1 = self.minBy(identity[A1])
def minBy[B](f: A => B)(implicit ordering: Ordering[B]): A = self.fold(Ordering.by(f).min)
def nonEmpty: Boolean = !self.isEmpty
def product[A1 >: A](implicit numeric: Numeric[A1]): A1 = self.fold(numeric.times)
def reverse: BinaryTree[A] = self match {
case Leaf(a) => Leaf(a)
case Branch(l, r) => Branch(r.reverse, l.reverse)
}
def sum[A1 >: A](implicit numeric: Numeric[A1]): A1 = self.fold(numeric.plus)
def size: Int = self.count(_ => true)
}
val tree = branch(
branch(
branch(
leaf(5),
leaf(10)
),
branch(
leaf(7),
leaf(8)
)
),
branch(
branch(
leaf(1),
leaf(11)
),
branch(
leaf(17),
leaf(21)
)
)
)
Solution
println(s"Minimum: ${tree.min}")
println(s"Maximum: ${tree.max}")
println(s"Number of elements: ${tree.size}")
println(s"Number of elements greater than 5: ${tree.count(_ > 5)}")
println(s"Does the tree contain the number 20?: ${tree.contains(20)}")
println(s"Does the tree contain negative numbers?: ${tree.exists(_ < 0)}")
println(s"Are all elements in the tree positive numbers?: ${tree.forall(_ > 0)}")
println(s"What's the first element greater than 5?: ${tree.find(_ > 5)}")
println(s"Is the tree empty?: ${tree.isEmpty}")
println(s"Is the tree non empty?: ${tree.nonEmpty}")
println(s"What's the sum of the elements (1st approach)?: ${tree.fold(_ + _)}")
println(s"What's the sum of the elements (2nd approach)?: ${tree.sum}")
println(s"What's the product of the elements?: ${tree.product}")
pprint.pprintln(s"Reversed tree: ${tree.reverse}")
Solution
Example 6
Obtain the following information from a Binary Tree of Person:
● Who’s the youngest person?
● Who’s the oldest person?
● What’s the average age?
● How many people are there per location?
Solution
sealed trait BinaryTree[+A] { self =>
import BinaryTree._
...
def groupBy[K](f: A => K): Map[K, List[A]] = self match {
case Leaf(a) => Map(f(a) -> List(a))
case Branch(l, r) =>
val leftMap = l.groupBy(f)
val rightMap = r.groupBy(f)
(leftMap.keySet ++ rightMap.keySet).map { key =>
(leftMap.get(key), rightMap.get(key)) match {
case (Some(as1), Some(as2)) => key -> (as1 ++ as2)
case (Some(as1), None) => key -> as1
case (None, Some(as2)) => key -> as2
case _ => throw new Error("Boom!")
}
}.toMap
}
...
}
Solution
val tree = branch(
branch(
branch(
leaf(Person("Adam", "Peterson", "USA", "California", 40)),
leaf(Person("Rachel", "Johns", "USA", "Los Angeles", 20))
),
branch(
leaf(Person("Jose", "Perez", "Bolivia", "La Paz", 35)),
leaf(Person("Monica", "Simpson", "UK", "London", 65))
)
),
branch(
branch(
leaf(Person("David", "Johnson", "USA", "California", 32)),
leaf(Person("Ana", "Sanchez", "Bolivia", "La Paz", 27))
),
branch(
leaf(Person("Laura", "Adams", "UK", "London", 54)),
leaf(Person("Roberto", "Mendes", "Brazil", "Minas Gerais", 24))
)
)
)
println(s"Youngest person: ${tree.minBy(_.age)}")
println(s"Oldest person: ${tree.maxBy(_.age)}")
println(s"What's the average age?: ${tree.map(_.age).sum / tree.size}")
println(
s"How many people are there per location?: ${tree.groupBy(person => (person.country, person.state)).mapValues(_.length)}"
)
Example 7
Process a Binary Tree of Strings, sending them to a server which just
echoes the received messages (encapsulated inside Future), and return
a Future of a Binary Tree containing the responses.
Solution
sealed trait BinaryTree[+A] { self =>
import BinaryTree._
...
def foreachFuture[B](f: A => Future[B]): Future[BinaryTree[B]] = self match {
case Leaf(a) => f(a).map(Leaf(_))
case Branch(l, r) => l.foreachFuture(f).zipWith(r.foreachFuture(f))(Branch(_, _))
}
...
}
Solution
def echo(message: String): Future[String] =
Future {
Thread.sleep(1000)
s"Echo: $message"
}
val messages =
branch(
branch(
leaf("message 1"),
leaf("message 2")
),
branch(
branch(
leaf("message 3"),
leaf("message 4")
),
branch(
leaf("message 5"),
leaf("message 6")
)
)
)
val responses = messages.foreachFuture(echo)
pprint.pprintln(Await.result(responses, 5.seconds))
/*
Branch(
Branch(Leaf("Echo: message 1"), Leaf("Echo: message 2")),
Branch(
Branch(Leaf("Echo: message 3"), Leaf("Echo: message 4")),
Branch(Leaf("Echo: message 5"), Leaf("Echo: message 6"))
)
)
*/
Solution
In conclusion
Enter
ZIO Prelude
What is ZIO Prelude?
Scala-first take on Functional Abstractions
ZIO Prelude gives us...
Data types that complement the Scala Standard Library:
● NonEmptyList
● NonEmptySet
● ZSet
● ZNonEmptySet
● Validation
● ZPure
ZIO Prelude gives us...
Newtypes that allow to increase type safety in domain
modeling, wrapping an existing type without adding any
runtime overhead.
ZIO Prelude gives us...
Typeclasses to describe similarities across different types, so
we can eliminate duplication/boilerplate:
● Business entities (Person, ShoppingCart, etc.)
● Effect-like structures (Try, Option, Future, Either, etc.)
● Collection-like structures (List, Tree, etc.)
Solving the
Boilerplate
Problem with
ZIO Prelude
Example 1
Let's suppose we have some cache stats data, organized
by date and application. This data comes from two
sources, and we need to combine them into one, by
summing counters when collisions exist.
Solution: Associative Typeclass
trait Associative[A] {
def combine(l: => A, r: => A): A
}
// Associativity law
(a1 <> (a2 <> a3)) <-> ((a1 <> a2) <> a3)
Solution: Associative Typeclass
ZIO Prelude has Associativeinstances for Scala Standard Types:
● Boolean
● Byte
● Char
● Short
● Int
● Long
● Float
● Double
● String
● Option
● Vector
● List
● Set
● Tuple
Solution: Associative Typeclass
And, of course, we can create instances of Associative for our
own types (or types on third party libraries)!
Solution: Associative Typeclass
final case class CacheStats(
entryCount: Option[Sum[Int]],
memorySize: Option[Sum[Long]],
hits: Option[Sum[Long]],
misses: Option[Sum[Long]],
loads: Option[Sum[Long]],
evictions: Option[Sum[Long]]
)
import zio.prelude._
object CacheStats {
def make(
entryCount: Option[Int],
memorySize: Option[Long],
hits: Option[Long],
misses: Option[Long],
loads: Option[Long],
evictions: Option[Long]
): CacheStats =
CacheStats(
entryCount.map(Sum(_)),
memorySize.map(Sum(_)),
hits.map(Sum(_)),
misses.map(Sum(_)),
loads.map(Sum(_)),
evictions.map(Sum(_))
)
implicit val associative = Associative.make[CacheStats] { (l, r) =>
CacheStats(
l.entryCount <> r.entryCount,
l.memorySize <> r.memorySize,
l.hits <> r.hits,
l.misses <> r.misses,
l.loads <> r.loads,
l.evictions <> r.evictions
)
}
}
Solution: Associative Typeclass
Solution: Associative Typeclass
● All typeclasses in ZIO Prelude include a set of laws that
instances must obey.
● We can use ZIO Test to check that laws of a typeclass are
fulfilled by a given instance, using Property Based Testing.
Solution: Associative Typeclass
object CacheStatsSpec extends DefaultRunnableSpec {
def spec = suite("CacheStatsSpec")(
suite("CacheStats")(
testM("associative")(checkAllLaws(Associative)(cacheStatsGen))
)
)
def cacheStatsGen[R <: Random with Sized]: Gen[R, CacheStats] = {
val intGen = Gen.oneOf(Gen.none, Gen.anyInt.map(Sum(_)).map(Some(_)))
val longGen = Gen.oneOf(Gen.none, Gen.anyLong.map(Sum(_)).map(Some(_)))
for {
entryCount <- intGen
memorySize <- longGen
hits <- longGen
misses <- longGen
loads <- longGen
evictions <- longGen
} yield CacheStats(entryCount, memorySize, hits, misses, loads, evictions)
}
}
Solution: Associative Typeclass
import zio.prelude._
pprint.pprintln(
Associative[Map[LocalDate, Map[String, CacheStats]]].combine(
stats1, stats2
)
)
/*
Map(
2020-01-18 -> Map(
"App X" -> CacheStats(Some(1000), Some(10000L), Some(4000L), Some(1000L),
Some(10000L), Some(5000L)),
"App Y" -> CacheStats(Some(800), Some(3500L), Some(6200L), None,
Some(15780L), Some(3026L)),
"App Z" -> CacheStats(None, Some(678L), Some(500L), None, Some(1600L), None)
),
2020-01-19 -> Map(
"App A" -> CacheStats(None, None, Some(8196L), None, Some(10836L), None),
"App B" -> CacheStats(Some(3134), Some(2854L), Some(8196L), Some(2000L),
Some(10836L), Some(6000L)),
"App C" -> CacheStats(None, None, Some(1000L), Some(467L), Some(1600L),
None)
),
2020-03-05 -> Map(
"App A" -> CacheStats(Some(8756), Some(1000L), Some(6420L), Some(2000L),
None, None),
"App Y" -> CacheStats(None, Some(1345L), Some(18064L), Some(246L), None,
None)
),
2020-04-10 -> Map("App X" -> CacheStats(Some(1879), None, Some(864L), None,
Some(5082L), None))
)
*/
val stats1: Map[LocalDate, Map[String, CacheStats]] = Map(
LocalDate.of(2020, 1, 18) -> Map(
"App X" -> CacheStats.make(Some(1000), Some(5000), Some(2000), Some(500), Some(5000), Some(2500)),
"App Y" -> CacheStats.make(None, Some(3500), Some(3100), None, Some(7890), Some(1513)),
"App Z" -> CacheStats.make(None, None, Some(500), None, Some(800), None)
),
LocalDate.of(2020, 1, 19) -> Map(
"App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None),
"App B" -> CacheStats.make(Some(1567), Some(2854), Some(4098), Some(1000), Some(5418), Some(3000)),
"App C" -> CacheStats.make(None, None, Some(500), None, Some(800), None)
),
LocalDate.of(2020, 3, 5) -> Map(
"App A" -> CacheStats.make(Some(4378), Some(1000), Some(3210), Some(1000), None, None),
"App Y" -> CacheStats.make(None, None, Some(9032), Some(123), None, None)
),
LocalDate.of(2020, 4, 10) -> Map(
"App X" -> CacheStats.make(Some(1879), None, Some(432), None, Some(2541), None)
)
)
val stats2: Map[LocalDate, Map[String, CacheStats]] = Map(
LocalDate.of(2020, 1, 18) -> Map(
"App X" -> CacheStats.make(None, Some(5000), Some(2000), Some(500), Some(5000), Some(2500)),
"App Y" -> CacheStats.make(Some(800), None, Some(3100), None, Some(7890), Some(1513)),
"App Z" -> CacheStats.make(None, Some(678), None, None, Some(800), None)
),
LocalDate.of(2020, 1, 19) -> Map(
"App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None),
"App B" -> CacheStats.make(Some(1567), None, Some(4098), Some(1000), Some(5418), Some(3000)),
"App C" -> CacheStats.make(None, None, Some(500), Some(467), Some(800), None)
),
LocalDate.of(2020, 3, 5) -> Map(
"App A" -> CacheStats.make(Some(4378), None, Some(3210), Some(1000), None, None),
"App Y" -> CacheStats.make(None, Some(1345), Some(9032), Some(123), None, None)
),
LocalDate.of(2020, 4, 10) -> Map(
"App X" -> CacheStats.make(None, None, Some(432), None, Some(2541), None)
)
)
Solution: Associative Typeclass
import zio.prelude._
pprint.pprintln(stats1 <> stats2)
/*
Map(
2020-01-18 -> Map(
"App X" -> CacheStats(Some(1000), Some(10000L), Some(4000L),
Some(1000L), Some(10000L), Some(5000L)),
"App Y" -> CacheStats(Some(800), Some(3500L), Some(6200L), None,
Some(15780L), Some(3026L)),
"App Z" -> CacheStats(None, Some(678L), Some(500L), None, Some(1600L),
None)
),
2020-01-19 -> Map(
"App A" -> CacheStats(None, None, Some(8196L), None, Some(10836L),
None),
"App B" -> CacheStats(Some(3134), Some(2854L), Some(8196L),
Some(2000L), Some(10836L), Some(6000L)),
"App C" -> CacheStats(None, None, Some(1000L), Some(467L), Some(1600L),
None)
),
2020-03-05 -> Map(
"App A" -> CacheStats(Some(8756), Some(1000L), Some(6420L),
Some(2000L), None, None),
"App Y" -> CacheStats(None, Some(1345L), Some(18064L), Some(246L),
None, None)
),
2020-04-10 -> Map("App X" -> CacheStats(Some(1879), None, Some(864L),
None, Some(5082L), None))
)
*/
val stats1: Map[LocalDate, Map[String, CacheStats]] = Map(
LocalDate.of(2020, 1, 18) -> Map(
"App X" -> CacheStats.make(Some(1000), Some(5000), Some(2000), Some(500), Some(5000), Some(2500)),
"App Y" -> CacheStats.make(None, Some(3500), Some(3100), None, Some(7890), Some(1513)),
"App Z" -> CacheStats.make(None, None, Some(500), None, Some(800), None)
),
LocalDate.of(2020, 1, 19) -> Map(
"App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None),
"App B" -> CacheStats.make(Some(1567), Some(2854), Some(4098), Some(1000), Some(5418), Some(3000)),
"App C" -> CacheStats.make(None, None, Some(500), None, Some(800), None)
),
LocalDate.of(2020, 3, 5) -> Map(
"App A" -> CacheStats.make(Some(4378), Some(1000), Some(3210), Some(1000), None, None),
"App Y" -> CacheStats.make(None, None, Some(9032), Some(123), None, None)
),
LocalDate.of(2020, 4, 10) -> Map(
"App X" -> CacheStats.make(Some(1879), None, Some(432), None, Some(2541), None)
)
)
val stats2: Map[LocalDate, Map[String, CacheStats]] = Map(
LocalDate.of(2020, 1, 18) -> Map(
"App X" -> CacheStats.make(None, Some(5000), Some(2000), Some(500), Some(5000), Some(2500)),
"App Y" -> CacheStats.make(Some(800), None, Some(3100), None, Some(7890), Some(1513)),
"App Z" -> CacheStats.make(None, Some(678), None, None, Some(800), None)
),
LocalDate.of(2020, 1, 19) -> Map(
"App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None),
"App B" -> CacheStats.make(Some(1567), None, Some(4098), Some(1000), Some(5418), Some(3000)),
"App C" -> CacheStats.make(None, None, Some(500), Some(467), Some(800), None)
),
LocalDate.of(2020, 3, 5) -> Map(
"App A" -> CacheStats.make(Some(4378), None, Some(3210), Some(1000), None, None),
"App Y" -> CacheStats.make(None, Some(1345), Some(9032), Some(123), None, None)
),
LocalDate.of(2020, 4, 10) -> Map(
"App X" -> CacheStats.make(None, None, Some(432), None, Some(2541), None)
)
)
Solution: Associative Typeclass
Example 2
Let's suppose we have a List of cache stats data, and we
want to sum all stats.
Solution: Identity Typeclass
trait Identity[A] extends Associative[A] {
def identity: A
}
// Associativity law
(a1 <> (a2 <> a3)) <-> ((a1 <> a2) <> a3)
// Left Identity law
(identity <> a) <-> a
// Right Identity law
(a <> identity) <-> a
Solution: Identity Typeclass
final case class CacheStats(
entryCount: Option[Sum[Int]],
memorySize: Option[Sum[Long]],
hits: Option[Sum[Long]],
misses: Option[Sum[Long]],
loads: Option[Sum[Long]],
evictions: Option[Sum[Long]]
)
import zio.prelude._
object CacheStats {
def make(
entryCount: Option[Int],
memorySize: Option[Long],
hits: Option[Long],
misses: Option[Long],
loads: Option[Long],
evictions: Option[Long]
): CacheStats =
CacheStats(
entryCount.map(Sum(_)),
memorySize.map(Sum(_)),
hits.map(Sum(_)),
misses.map(Sum(_)),
loads.map(Sum(_)),
evictions.map(Sum(_))
)
implicit val identity = Identity.make[CacheStats](
CacheStats.make(None, None, None, None, None, None),
(l, r) =>
CacheStats(
l.entryCount <> r.entryCount,
l.memorySize <> r.memorySize,
l.hits <> r.hits,
l.misses <> r.misses,
l.loads <> r.loads,
l.evictions <> r.evictions
)
)
}
Solution: Identity Typeclass
def sum(cacheStats: List[CacheStats]): CacheStats =
cacheStats.foldRight(Identity[CacheStats].identity)(_ <> _)
def main(args: Array[String]): Unit = {
val cacheStats1 = List(
CacheStats.make(Some(1000), Some(5000), Some(2000), Some(500), Some(5000), Some(2500)),
CacheStats.make(None, Some(3500), Some(3100), None, Some(7890), Some(1513)),
CacheStats.make(None, None, Some(500), None, Some(800), None)
)
val cacheStats2 = List.empty[CacheStats]
println(sum(cacheStats1)) // CacheStats(Some(1000), Some(8500L), Some(5600L), Some(500L), Some(13690L), Some(4013L))
println(sum(cacheStats2)) // CacheStats(None, None, None, None, None, None)
}
Solution: Identity Typeclass
Example 3
Let's suppose we have several Options and we want to
combine them into an Option of a Tuple
Solution: AssociativeBoth Typeclass
trait AssociativeBoth[F[_]] {
def both[A, B](fa: => F[A], fb: => F[B]): F[(A, B)]
}
// Associativity law
both(fa, both(fb, fc)) ~ both(both(fa, fb), fc)
Solution: AssociativeBoth Typeclass
ZIO Prelude has AssociativeBothinstances for Scala Standard Types:
● Either
● Future
● List
● Option
● Try
● Vector
Solution: AssociativeBoth Typeclass
import zio.prelude._
def main(args: Array[String]): Unit = {
val option1 = Option(1)
val option2 = Option(2)
val option3 = Option(3)
val option4 = Option(4)
val option5 = Option(5)
val option6 = Option(6)
val option7 = Option(7)
val option8 = Option(8)
val option9 = Option(9)
val option10 = Option(10)
println {
AssociativeBoth.tupleN(option1, option2, option3, option4, option5, option6, option7, option8, option9, option10)
}
// Some(Tuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
}
Solution: AssociativeBoth Typeclass
Example 4
Let's suppose we request some Person data from three
different servers, and we want to return the first successful
response, or a failure if all the requests fail
Solution: AssociativeEither Typeclass
trait AssociativeEither[F[_]] {
def either[A, B](fa: => F[A], fb: => F[B]): F[Either[A, B]]
}
// Associativity law
either(fa, either(fb, fc)) ~ either(either(fa, fb), fc)
Solution: AssociativeEither Typeclass
import com.twitter.util.{ Return, Throw, Try }
import zio.prelude._
implicit val TryAssociativeEither = new AssociativeEither[Try] {
def either[A, B](fa: => Try[A], fb: => Try[B]): Try[Either[A, B]] =
fa.map(Left(_)) rescue {
case _ => fb.map(Right(_))
}
}
Solution: AssociativeEither Typeclass
import com.twitter.util.{ Return, Throw, Try }
import zio.prelude._
final case class Person(firstName: String, lastName: String, country: String, state: String, age: Int)
def getPerson(url: String): Try[Person] =
if (url.contains("1") || url.contains("2")) Throw(new Exception("Server error"))
else Return(Person("Ana", "Perez", "Bolivia", "La Paz", 30))
def main(args: Array[String]): Unit = {
lazy val response1 = getPerson("http://server1.com/info")
lazy val response2 = getPerson("http://server2.com/info")
lazy val response3 = getPerson("http://server3.com/info")
val response: Try[Person] = response1 orElse response2 orElse response3
println(response)
// Return(Person("Ana", "Perez", "Bolivia", "La Paz", 30))
}
Solution: AssociativeEither Typeclass
Example 5
Obtain the following information from a Binary Tree of Integers:
● The minimum value
● The maximum value
● The number of elements
● The number of elements greater than 5
● Does it contain the number 20?
● Does it contain negative numbers?
● Are all elements positive numbers?
● What’s the first element greater than 5?
● Is the tree empty?
● Is the tree non empty?
● What’s the sum of the elements?
● What’s the product of the elements?
● What’s the reversed tree?
Solution: Traversable Typeclass
trait Traversable[F[+_]] extends Covariant[F] {
def foreach[G[+_]: IdentityBoth: Covariant, A, B](fa: F[A])(f: A => G[B]): G[F[B]]
// A lot of methods for free!
def contains[A, A1 >: A](fa: F[A])(a: A1)(implicit A: Equal[A1]): Boolean
def count[A](fa: F[A])(f: A => Boolean): Int
def exists[A](fa: F[A])(f: A => Boolean): Boolean
def find[A](fa: F[A])(f: A => Boolean): Option[A]
def flip[G[+_]: IdentityBoth: Covariant, A](fa: F[G[A]]): G[F[A]]
def fold[A: Identity](fa: F[A]): A
def foldLeft[S, A](fa: F[A])(s: S)(f: (S, A) => S): S
def foldMap[A, B: Identity](fa: F[A])(f: A => B): B
def foldRight[S, A](fa: F[A])(s: S)(f: (A, S) => S): S
def forall[A](fa: F[A])(f: A => Boolean): Boolean
def foreach_[G[+_]: IdentityBoth: Covariant, A](fa: F[A])(f: A => G[Any]): G[Unit]
def isEmpty[A](fa: F[A]): Boolean
def map[A, B](f: A => B): F[A] => F[B]
def mapAccum[S, A, B](fa: F[A])(s: S)(f: (S, A) => (S, B)): (S, F[B])
def maxOption[A: Ord](fa: F[A]): Option[A]
def maxByOption[A, B: Ord](fa: F[A])(f: A => B): Option[A]
def minOption[A: Ord](fa: F[A]): Option[A]
def minByOption[A, B: Ord](fa: F[A])(f: A => B): Option[A]
def nonEmpty[A](fa: F[A]): Boolean
def product[A](fa: F[A])(implicit ev: Identity[Prod[A]]): A
def reduceMapOption[A, B: Associative](fa: F[A])(f: A => B): Option[B]
def reduceOption[A](fa: F[A])(f: (A, A) => A): Option[A]
def reverse[A](fa: F[A]): F[A]
def size[A](fa: F[A]): Int
def sum[A](fa: F[A])(implicit ev: Identity[Sum[A]]): A
def toChunk[A](fa: F[A]): Chunk[A]
def toList[A](fa: F[A]): List[A]
def zipWithIndex[A](fa: F[A]): F[(A, Int)]
}
Solution: Traversable Typeclass
sealed trait BinaryTree[+A] { self =>
import BinaryTree._
def map[B](f: A => B): BinaryTree[B] = self match {
case Leaf(a) => Leaf(f(a))
case Branch(left, right) => Branch(left.map(f), right.map(f))
}
}
object BinaryTree {
private final case class Leaf[A](value: A) extends BinaryTree[A]
private final case class Branch[A](left: BinaryTree[A], right: BinaryTree[A]) extends BinaryTree[A]
def leaf[A](value: A): BinaryTree[A] = BinaryTree.Leaf(value)
def branch[A](left: BinaryTree[A], right: BinaryTree[A]): BinaryTree[A] = BinaryTree.Branch(left, right)
implicit val traversable = new Traversable[BinaryTree] {
def foreach[G[+ _]: IdentityBoth: Covariant, A, B](fa: BinaryTree[A])(f: A => G[B]): G[BinaryTree[B]] = fa match {
case Leaf(a) => f(a).map(Leaf(_))
case Branch(l, r) => foreach(l)(f).zipWith(foreach(r)(f))(Branch(_, _))
}
override def map[A, B](f: A => B): BinaryTree[A] => BinaryTree[B] = _.map(f)
}
}
val tree = branch(
branch(
branch(
leaf(5),
leaf(10)
),
branch(
leaf(7),
leaf(8)
)
),
branch(
branch(
leaf(1),
leaf(11)
),
branch(
leaf(17),
leaf(21)
)
)
)
Solution: Traversable Typeclass
println(s"Minimum: ${tree.minOption}")
println(s"Maximum: ${tree.maxOption}")
println(s"Number of elements: ${tree.size}")
println(s"Number of elements greater than 5: ${tree.count(_ > 5)}")
println(s"Does the tree contain the number 20?: ${tree.contains(20)}")
println(s"Does the tree contain negative numbers?: ${tree.exists(_ < 0)}")
println(s"Are all elements in the tree positive numbers?: ${tree.forall(_ > 0)}")
println(s"What's the first element greater than 5?: ${tree.find(_ > 5)}")
println(s"Is the tree empty?: ${tree.isEmpty}")
println(s"Is the tree non empty?: ${tree.nonEmpty}")
println(s"What's the sum of the elements: ${tree.sum}")
println(s"What's the product of the elements?: ${tree.product}")
pprint.pprintln(s"Reversed tree: ${tree.reverse}")
Solution: Traversable Typeclass
Example 6
Obtain the following information from a Binary Tree of Person:
● Who’s the youngest person?
● Who’s the oldest person?
● What’s the average age?
● How many people are there per location?
Solution: Traversable Typeclass
val tree = branch(
branch(
branch(
leaf(Person("Adam", "Peterson", "USA", "California", 40)),
leaf(Person("Rachel", "Johns", "USA", "Los Angeles", 20))
),
branch(
leaf(Person("Jose", "Perez", "Bolivia", "La Paz", 35)),
leaf(Person("Monica", "Simpson", "UK", "London", 65))
)
),
branch(
branch(
leaf(Person("David", "Johnson", "USA", "California", 32)),
leaf(Person("Ana", "Sanchez", "Bolivia", "La Paz", 27))
),
branch(
leaf(Person("Laura", "Adams", "UK", "London", 54)),
leaf(Person("Roberto", "Mendes", "Brazil", "Minas Gerais", 24))
)
)
)
println(s"Youngest person: ${tree.minByOption(_.age)}")
println(s"Oldest person: ${tree.maxByOption(_.age)}")
println(s"What's the average age?: ${tree.map(_.age).sum / tree.size}")
println(
s"How many people are there per location?: ${Traversable[BinaryTree].groupBy(tree)(person => (person.country, person.state)).mapValues(_.length)}"
)
Solution: NonEmptyTraversable Typeclass
trait NonEmptyTraversable[F[+_]] extends Traversable[F] {
def foreach1[G[+_]: AssociativeBoth: Covariant, A, B](fa: F[A])(f: A => G[B]): G[F[B]]
def flip1[G[+_]: AssociativeBoth: Covariant, A](fa: F[G[A]]): G[F[A]]
override def foreach[G[+_]: IdentityBoth: Covariant, A, B](fa: F[A])(f: A => G[B]): G[F[B]]
def foreach1_[G[+_]: AssociativeBoth: Covariant, A](fa: F[A])(f: A => G[Any]): G[Unit]
def max[A: Ord](fa: F[A]): A
def maxBy[A, B: Ord](fa: F[A])(f: A => B): A
def min[A: Ord](fa: F[A]): A
def minBy[A, B: Ord](fa: F[A])(f: A => B): A
def reduce[A](fa: F[A])(f: (A, A) => A): A
def reduce1[A: Associative](fa: F[A]): A
def reduceMap[A, B: Associative](fa: F[A])(f: A => B): B
def reduceMapLeft[A, B](fa: F[A])(map: A => B)(reduce: (B, A) => B): B
def reduceMapRight[A, B](fa: F[A])(map: A => B)(reduce: (A, B) => B): B
def toNonEmptyChunk[A](fa: F[A]): NonEmptyChunk[A]
def toNonEmptyList[A](fa: F[A]): NonEmptyList[A]
}
Solution: NonEmptyTraversable Typeclass
sealed trait BinaryTree[+A] { self =>
import BinaryTree._
def map[B](f: A => B): BinaryTree[B] = self match {
case Leaf(a) => Leaf(f(a))
case Branch(left, right) => Branch(left.map(f), right.map(f))
}
}
object BinaryTree {
private final case class Leaf[A](value: A) extends BinaryTree[A]
private final case class Branch[A](left: BinaryTree[A], right: BinaryTree[A]) extends BinaryTree[A]
def leaf[A](value: A): BinaryTree[A] = BinaryTree.Leaf(value)
def branch[A](left: BinaryTree[A], right: BinaryTree[A]): BinaryTree[A] = BinaryTree.Branch(left, right)
implicit val nonEmptyTraversable = new NonEmptyTraversable[BinaryTree] {
def foreach1[G[+ _]: AssociativeBoth: Covariant, A, B](fa: BinaryTree[A])(f: A => G[B]): G[BinaryTree[B]] = fa match {
case Leaf(a) => f(a).map(Leaf(_))
case Branch(l, r) => foreach1(l)(f).zipWith(foreach1(r)(f))(Branch(_, _))
}
}
}
Solution: NonEmptyTraversable Typeclass
val tree = branch(
branch(
branch(
leaf(Person("Adam", "Peterson", "USA", "California", 40)),
leaf(Person("Rachel", "Johns", "USA", "Los Angeles", 20))
),
branch(
leaf(Person("Jose", "Perez", "Bolivia", "La Paz", 35)),
leaf(Person("Monica", "Simpson", "UK", "London", 65))
)
),
branch(
branch(
leaf(Person("David", "Johnson", "USA", "California", 32)),
leaf(Person("Ana", "Sanchez", "Bolivia", "La Paz", 27))
),
branch(
leaf(Person("Laura", "Adams", "UK", "London", 54)),
leaf(Person("Roberto", "Mendes", "Brazil", "Minas Gerais", 24))
)
)
)
println(s"Youngest person: ${NonEmptyTraversable[BinaryTree].minBy(tree)(_.age)}")
println(s"Oldest person: ${NonEmptyTraversable[BinaryTree].maxBy(tree)(_.age)}")
Solution: NonEmptyTraversable Typeclass
Example 7
Process a Binary Tree of Strings, sending them to a server which just
echoes the received messages (encapsulated inside Future), and return
a Future of a Binary Tree containing the responses.
Solution: Traversable Typeclass
def echo(message: String): Future[String] =
Future {
Thread.sleep(1000)
s"Echo: $message"
}
val messages =
branch(
branch(
leaf("message 1"),
leaf("message 2")
),
branch(
branch(
leaf("message 3"),
leaf("message 4")
),
branch(
leaf("message 5"),
leaf("message 6")
)
)
)
val responses = messages.foreach(echo)
pprint.pprintln(Await.result(responses, 5.seconds))
/*
Branch(
Branch(Leaf("Echo: message 1"), Leaf("Echo: message 2")),
Branch(
Branch(Leaf("Echo: message 3"), Leaf("Echo: message 4")),
Branch(Leaf("Echo: message 5"), Leaf("Echo: message 6"))
)
)
*/
In conclusion
Summary
● There's a lot of boilerplate in our applications
● Abstraction is the key to eliminating boilerplate
● Functional code uses typeclasses for abstraction
Summary
ZIO Prelude has typeclasses that let you eliminate boilerplate across:
● Business entities:
○ Associative/Identity
● Effect-like structures:
○ AssociativeBoth/IdentityBoth
○ AssociativeEither/IdentityEither
● Collection-like structures:
○ Traversable/NonEmptyTraversable
Special thanks
● To Functional Scala organizers for hosting this presentation
● To John De Goes for guidance and support
Thank You!
Where to learn more
● ZIO Prelude on Github: https://github.com/zio/zio-prelude/
● SF Scala: Reimagining Functional Type Classes, talk by
John De Goes and Adam Fraser
● Functional World: Exploring ZIO Prelude - The game
changer for typeclasses in Scala, talk by Jorge Vásquez
@jorvasquez2301
jorge.vasquez@scalac.io
jorge-vasquez-2301
Contact me
The Terror-Free Guide to Introducing Functional Scala at Work

More Related Content

What's hot

Alfresco Development Framework Basic
Alfresco Development Framework BasicAlfresco Development Framework Basic
Alfresco Development Framework BasicMario Romano
 
Functional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorldFunctional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorldJorge Vásquez
 
Purely Functional Data Structures in Scala
Purely Functional Data Structures in ScalaPurely Functional Data Structures in Scala
Purely Functional Data Structures in ScalaVladimir Kostyukov
 
Java Basics
Java BasicsJava Basics
Java BasicsSunil OS
 
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...Edureka!
 
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...Chris Richardson
 
How to make APEX print through Node.js
How to make APEX print through Node.jsHow to make APEX print through Node.js
How to make APEX print through Node.jsDimitri Gielis
 
Declarative Concurrency with Reactive Programming
Declarative Concurrency with Reactive ProgrammingDeclarative Concurrency with Reactive Programming
Declarative Concurrency with Reactive ProgrammingFlorian Stefan
 
ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022Alexander Ioffe
 
Collections - Maps
Collections - Maps Collections - Maps
Collections - Maps Hitesh-Java
 

What's hot (20)

27 applet programming
27  applet programming27  applet programming
27 applet programming
 
Alfresco Development Framework Basic
Alfresco Development Framework BasicAlfresco Development Framework Basic
Alfresco Development Framework Basic
 
Functional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorldFunctional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorld
 
Purely Functional Data Structures in Scala
Purely Functional Data Structures in ScalaPurely Functional Data Structures in Scala
Purely Functional Data Structures in Scala
 
Collections and generics
Collections and genericsCollections and generics
Collections and generics
 
Spring data jpa
Spring data jpaSpring data jpa
Spring data jpa
 
Optional in Java 8
Optional in Java 8Optional in Java 8
Optional in Java 8
 
Java Basics
Java BasicsJava Basics
Java Basics
 
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...
 
Android intents
Android intentsAndroid intents
Android intents
 
Collection framework
Collection frameworkCollection framework
Collection framework
 
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
 
Function in C
Function in CFunction in C
Function in C
 
How to make APEX print through Node.js
How to make APEX print through Node.jsHow to make APEX print through Node.js
How to make APEX print through Node.js
 
Java 8 Lambda and Streams
Java 8 Lambda and StreamsJava 8 Lambda and Streams
Java 8 Lambda and Streams
 
Exception handling in Java
Exception handling in JavaException handling in Java
Exception handling in Java
 
Declarative Concurrency with Reactive Programming
Declarative Concurrency with Reactive ProgrammingDeclarative Concurrency with Reactive Programming
Declarative Concurrency with Reactive Programming
 
Clean code
Clean codeClean code
Clean code
 
ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022
 
Collections - Maps
Collections - Maps Collections - Maps
Collections - Maps
 

Similar to The Terror-Free Guide to Introducing Functional Scala at Work

Introduction To PostGIS
Introduction To PostGISIntroduction To PostGIS
Introduction To PostGISmleslie
 
FOSS4G 2010 PostGIS Raster: an Open Source alternative to Oracle GeoRaster
FOSS4G 2010 PostGIS Raster: an Open Source alternative to Oracle GeoRasterFOSS4G 2010 PostGIS Raster: an Open Source alternative to Oracle GeoRaster
FOSS4G 2010 PostGIS Raster: an Open Source alternative to Oracle GeoRasterJorge Arevalo
 
Mapping For Sharepoint T11 Peter Smith
Mapping For Sharepoint T11 Peter SmithMapping For Sharepoint T11 Peter Smith
Mapping For Sharepoint T11 Peter SmithSpatialSmith
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJSFestUA
 
Stratosphere Intro (Java and Scala Interface)
Stratosphere Intro (Java and Scala Interface)Stratosphere Intro (Java and Scala Interface)
Stratosphere Intro (Java and Scala Interface)Robert Metzger
 
2016 gunma.web games-and-asm.js
2016 gunma.web games-and-asm.js2016 gunma.web games-and-asm.js
2016 gunma.web games-and-asm.jsNoritada Shimizu
 
A Rusty introduction to Apache Arrow and how it applies to a time series dat...
A Rusty introduction to Apache Arrow and how it applies to a  time series dat...A Rusty introduction to Apache Arrow and how it applies to a  time series dat...
A Rusty introduction to Apache Arrow and how it applies to a time series dat...Andrew Lamb
 
Saving Gaia with GeoDjango
Saving Gaia with GeoDjangoSaving Gaia with GeoDjango
Saving Gaia with GeoDjangoCalvin Cheng
 
10. Kapusta, Stofanak - eGlu
10. Kapusta, Stofanak - eGlu10. Kapusta, Stofanak - eGlu
10. Kapusta, Stofanak - eGluMobCon
 
Python en la Plataforma ArcGIS
Python en la Plataforma ArcGISPython en la Plataforma ArcGIS
Python en la Plataforma ArcGISXander Bakker
 
Eric Lafortune - The Jack and Jill build system
Eric Lafortune - The Jack and Jill build systemEric Lafortune - The Jack and Jill build system
Eric Lafortune - The Jack and Jill build systemGuardSquare
 
Wprowadzenie do technologi Big Data i Apache Hadoop
Wprowadzenie do technologi Big Data i Apache HadoopWprowadzenie do technologi Big Data i Apache Hadoop
Wprowadzenie do technologi Big Data i Apache HadoopSages
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with ClojureDmitry Buzdin
 
Easy GPS Tracker using Arduino and Python
Easy GPS Tracker using Arduino and PythonEasy GPS Tracker using Arduino and Python
Easy GPS Tracker using Arduino and PythonNúria Vilanova
 

Similar to The Terror-Free Guide to Introducing Functional Scala at Work (20)

Introduction To PostGIS
Introduction To PostGISIntroduction To PostGIS
Introduction To PostGIS
 
FOSS4G 2010 PostGIS Raster: an Open Source alternative to Oracle GeoRaster
FOSS4G 2010 PostGIS Raster: an Open Source alternative to Oracle GeoRasterFOSS4G 2010 PostGIS Raster: an Open Source alternative to Oracle GeoRaster
FOSS4G 2010 PostGIS Raster: an Open Source alternative to Oracle GeoRaster
 
Mapping For Sharepoint T11 Peter Smith
Mapping For Sharepoint T11 Peter SmithMapping For Sharepoint T11 Peter Smith
Mapping For Sharepoint T11 Peter Smith
 
Analytics with Spark
Analytics with SparkAnalytics with Spark
Analytics with Spark
 
Hadoop I/O Analysis
Hadoop I/O AnalysisHadoop I/O Analysis
Hadoop I/O Analysis
 
Seeing Like Software
Seeing Like SoftwareSeeing Like Software
Seeing Like Software
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless Bebop
 
Stratosphere Intro (Java and Scala Interface)
Stratosphere Intro (Java and Scala Interface)Stratosphere Intro (Java and Scala Interface)
Stratosphere Intro (Java and Scala Interface)
 
2016 gunma.web games-and-asm.js
2016 gunma.web games-and-asm.js2016 gunma.web games-and-asm.js
2016 gunma.web games-and-asm.js
 
Pycon2011
Pycon2011Pycon2011
Pycon2011
 
A Rusty introduction to Apache Arrow and how it applies to a time series dat...
A Rusty introduction to Apache Arrow and how it applies to a  time series dat...A Rusty introduction to Apache Arrow and how it applies to a  time series dat...
A Rusty introduction to Apache Arrow and how it applies to a time series dat...
 
Saving Gaia with GeoDjango
Saving Gaia with GeoDjangoSaving Gaia with GeoDjango
Saving Gaia with GeoDjango
 
10. Kapusta, Stofanak - eGlu
10. Kapusta, Stofanak - eGlu10. Kapusta, Stofanak - eGlu
10. Kapusta, Stofanak - eGlu
 
Python en la Plataforma ArcGIS
Python en la Plataforma ArcGISPython en la Plataforma ArcGIS
Python en la Plataforma ArcGIS
 
Eric Lafortune - The Jack and Jill build system
Eric Lafortune - The Jack and Jill build systemEric Lafortune - The Jack and Jill build system
Eric Lafortune - The Jack and Jill build system
 
Wprowadzenie do technologi Big Data i Apache Hadoop
Wprowadzenie do technologi Big Data i Apache HadoopWprowadzenie do technologi Big Data i Apache Hadoop
Wprowadzenie do technologi Big Data i Apache Hadoop
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
 
Easy GPS Tracker using Arduino and Python
Easy GPS Tracker using Arduino and PythonEasy GPS Tracker using Arduino and Python
Easy GPS Tracker using Arduino and Python
 
JQuery Flot
JQuery FlotJQuery Flot
JQuery Flot
 
A More Flash Like Web?
A More Flash Like Web?A More Flash Like Web?
A More Flash Like Web?
 

More from Jorge Vásquez

Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!
Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!
Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!Jorge Vásquez
 
Programación Funcional 101 con Scala y ZIO 2.0
Programación Funcional 101 con Scala y ZIO 2.0Programación Funcional 101 con Scala y ZIO 2.0
Programación Funcional 101 con Scala y ZIO 2.0Jorge Vásquez
 
A Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIOA Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIOJorge Vásquez
 
Be Smart, Constrain Your Types to Free Your Brain!
Be Smart, Constrain Your Types to Free Your Brain!Be Smart, Constrain Your Types to Free Your Brain!
Be Smart, Constrain Your Types to Free Your Brain!Jorge Vásquez
 
Consiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIO
Consiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIOConsiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIO
Consiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIOJorge Vásquez
 
ZIO Prelude - ZIO World 2021
ZIO Prelude - ZIO World 2021ZIO Prelude - ZIO World 2021
ZIO Prelude - ZIO World 2021Jorge Vásquez
 
Exploring type level programming in Scala
Exploring type level programming in ScalaExploring type level programming in Scala
Exploring type level programming in ScalaJorge Vásquez
 
Introduction to programming with ZIO functional effects
Introduction to programming with ZIO functional effectsIntroduction to programming with ZIO functional effects
Introduction to programming with ZIO functional effectsJorge Vásquez
 

More from Jorge Vásquez (8)

Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!
Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!
Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!
 
Programación Funcional 101 con Scala y ZIO 2.0
Programación Funcional 101 con Scala y ZIO 2.0Programación Funcional 101 con Scala y ZIO 2.0
Programación Funcional 101 con Scala y ZIO 2.0
 
A Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIOA Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIO
 
Be Smart, Constrain Your Types to Free Your Brain!
Be Smart, Constrain Your Types to Free Your Brain!Be Smart, Constrain Your Types to Free Your Brain!
Be Smart, Constrain Your Types to Free Your Brain!
 
Consiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIO
Consiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIOConsiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIO
Consiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIO
 
ZIO Prelude - ZIO World 2021
ZIO Prelude - ZIO World 2021ZIO Prelude - ZIO World 2021
ZIO Prelude - ZIO World 2021
 
Exploring type level programming in Scala
Exploring type level programming in ScalaExploring type level programming in Scala
Exploring type level programming in Scala
 
Introduction to programming with ZIO functional effects
Introduction to programming with ZIO functional effectsIntroduction to programming with ZIO functional effects
Introduction to programming with ZIO functional effects
 

Recently uploaded

SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte GermanySuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte GermanyChristoph Pohl
 
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full RecordingOpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full RecordingShane Coughlan
 
Understanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM ArchitectureUnderstanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM Architecturerahul_net
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Developmentvyaparkranti
 
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odishasmiwainfosol
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...OnePlan Solutions
 
Sending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdfSending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdf31events.com
 
Large Language Models for Test Case Evolution and Repair
Large Language Models for Test Case Evolution and RepairLarge Language Models for Test Case Evolution and Repair
Large Language Models for Test Case Evolution and RepairLionel Briand
 
What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...Technogeeks
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Cizo Technology Services
 
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...Angel Borroy López
 
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptxReal-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptxRTS corp
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtimeandrehoraa
 
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingOpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingShane Coughlan
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...OnePlan Solutions
 
Ronisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited CatalogueRonisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited Catalogueitservices996
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Natan Silnitsky
 
Machine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringMachine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringHironori Washizaki
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceBrainSell Technologies
 

Recently uploaded (20)

SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte GermanySuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
 
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full RecordingOpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
 
Understanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM ArchitectureUnderstanding Flamingo - DeepMind's VLM Architecture
Understanding Flamingo - DeepMind's VLM Architecture
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Development
 
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
 
Sending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdfSending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdf
 
Large Language Models for Test Case Evolution and Repair
Large Language Models for Test Case Evolution and RepairLarge Language Models for Test Case Evolution and Repair
Large Language Models for Test Case Evolution and Repair
 
What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
 
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
 
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptxReal-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtime
 
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full RecordingOpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
OpenChain AI Study Group - Europe and Asia Recap - 2024-04-11 - Full Recording
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
 
Ronisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited CatalogueRonisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited Catalogue
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
 
Machine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringMachine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their Engineering
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. Salesforce
 

The Terror-Free Guide to Introducing Functional Scala at Work

  • 1. The Terror-Free Guide to Introducing Functional Scala at Work (without dying in the process)
  • 3.
  • 4. We are hiring Scala Developers! https://scalac.io/careers/
  • 5. Agenda ● Motivation ● ZIO Prelude Overview ● How ZIO Prelude can help us
  • 7. Example 1 Let's suppose we have some cache stats data, organized by date and application. This data comes from two sources, and we need to combine them into one, by summing counters when collisions exist.
  • 9. Solution 1 final case class CacheStats( entryCount: Option[Int], memorySize: Option[Long], hits: Option[Long], misses: Option[Long], loads: Option[Long], evictions: Option[Long] ) object CacheStats { def make( entryCount: Option[Int], memorySize: Option[Long], hits: Option[Long], misses: Option[Long], loads: Option[Long], evictions: Option[Long] ): CacheStats = CacheStats(entryCount, memorySize, hits, misses, loads, evictions) }
  • 10. Solution 1 pprint.pprintln(stats1 ++ stats2) /* Map( 2020-01-18 -> Map( "App X" -> CacheStats(None, Some(5000L), Some(2000L), Some(500L), Some(5000L), Some(2500L)), "App Y" -> CacheStats(Some(800), None, Some(3100L), None, Some(7890L), Some(1513L)), "App Z" -> CacheStats(None, Some(678L), None, None, Some(800L), None) ), 2020-01-19 -> Map( "App A" -> CacheStats(None, None, Some(4098L), None, Some(5418L), None), "App B" -> CacheStats(Some(1567), None, Some(4098L), Some(1000L), Some(5418L), Some(3000L)), "App C" -> CacheStats(None, None, Some(500L), Some(467L), Some(800L), None) ), 2020-03-05 -> Map( "App A" -> CacheStats(Some(4378), None, Some(3210L), Some(1000L), None, None), "App Y" -> CacheStats(None, Some(1345L), Some(9032L), Some(123L), None, None) ), 2020-04-10 -> Map("App X" -> CacheStats(None, None, Some(432L), None, Some(2541L), None)) ) */ val stats1: Map[LocalDate, Map[String, CacheStats]] = Map( LocalDate.of(2020, 1, 18) -> Map( "App X" -> CacheStats.make(Some(1000), Some(5000), Some(2000), Some(500), Some(5000), Some(2500)), "App Y" -> CacheStats.make(None, Some(3500), Some(3100), None, Some(7890), Some(1513)), "App Z" -> CacheStats.make(None, None, Some(500), None, Some(800), None) ), LocalDate.of(2020, 1, 19) -> Map( "App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None), "App B" -> CacheStats.make(Some(1567), Some(2854), Some(4098), Some(1000), Some(5418), Some(3000)), "App C" -> CacheStats.make(None, None, Some(500), None, Some(800), None) ), LocalDate.of(2020, 3, 5) -> Map( "App A" -> CacheStats.make(Some(4378), Some(1000), Some(3210), Some(1000), None, None), "App Y" -> CacheStats.make(None, None, Some(9032), Some(123), None, None) ), LocalDate.of(2020, 4, 10) -> Map( "App X" -> CacheStats.make(Some(1879), None, Some(432), None, Some(2541), None) ) ) val stats2: Map[LocalDate, Map[String, CacheStats]] = Map( LocalDate.of(2020, 1, 18) -> Map( "App X" -> CacheStats.make(None, Some(5000), Some(2000), Some(500), Some(5000), Some(2500)), "App Y" -> CacheStats.make(Some(800), None, Some(3100), None, Some(7890), Some(1513)), "App Z" -> CacheStats.make(None, Some(678), None, None, Some(800), None) ), LocalDate.of(2020, 1, 19) -> Map( "App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None), "App B" -> CacheStats.make(Some(1567), None, Some(4098), Some(1000), Some(5418), Some(3000)), "App C" -> CacheStats.make(None, None, Some(500), Some(467), Some(800), None) ), LocalDate.of(2020, 3, 5) -> Map( "App A" -> CacheStats.make(Some(4378), None, Some(3210), Some(1000), None, None), "App Y" -> CacheStats.make(None, Some(1345), Some(9032), Some(123), None, None) ), LocalDate.of(2020, 4, 10) -> Map( "App X" -> CacheStats.make(None, None, Some(432), None, Some(2541), None) ) )
  • 13. Solution 2 final case class CacheStats( entryCount: Option[Int], memorySize: Option[Long], hits: Option[Long], misses: Option[Long], loads: Option[Long], evictions: Option[Long] ) { self => def combine(that: CacheStats): CacheStats = { def combineCounters[A: Numeric](left: Option[A], right: Option[A]): Option[A] = (left, right) match { case (Some(l), Some(r)) => Some(implicitly[Numeric[A]].plus(l, r)) case (Some(l), None) => Some(l) case (None, Some(r)) => Some(r) case (None, None) => None } CacheStats( combineCounters(self.entryCount, that.entryCount), combineCounters(self.memorySize, that.memorySize), combineCounters(self.hits, that.hits), combineCounters(self.misses, that.misses), combineCounters(self.loads, that.loads), combineCounters(self.evictions, that.evictions) ) } }
  • 14. Solution 2 def combine( left: Map[LocalDate, Map[String, CacheStats]], right: Map[LocalDate, Map[String, CacheStats]] ): Map[LocalDate, Map[String, CacheStats]] = { (left.keySet ++ right.keySet).map { date => val newStatsByApp = (left.get(date), right.get(date)) match { case (Some(v1), None) => v1 case (None, Some(v2)) => v2 case (Some(v1), Some(v2)) => (v1.keySet ++ v2.keySet).map { location => val newStats = (v1.get(location), v2.get(location)) match { case (Some(s1), None) => s1 case (None, Some(s2)) => s2 case (Some(s1), Some(s2)) => s1 combine s2 case (None, None) => throw new Error("Unexpected scenario") } location -> newStats }.toMap case (None, None) => throw new Error("Unexpected scenario") } date -> newStatsByApp } }.toMap
  • 15. Solution 2 pprint.pprintln(combine(stats1, stats2)) /* Map( 2020-01-18 -> Map( "App X" -> CacheStats(Some(1000), Some(10000L), Some(4000L), Some(1000L), Some(10000L), Some(5000L)), "App Y" -> CacheStats(Some(800), Some(3500L), Some(6200L), None, Some(15780L), Some(3026L)), "App Z" -> CacheStats(None, Some(678L), Some(500L), None, Some(1600L), None) ), 2020-01-19 -> Map( "App A" -> CacheStats(None, None, Some(8196L), None, Some(10836L), None), "App B" -> CacheStats(Some(3134), Some(2854L), Some(8196L), Some(2000L), Some(10836L), Some(6000L)), "App C" -> CacheStats(None, None, Some(1000L), Some(467L), Some(1600L), None) ), 2020-03-05 -> Map( "App A" -> CacheStats(Some(8756), Some(1000L), Some(6420L), Some(2000L), None, None), "App Y" -> CacheStats(None, Some(1345L), Some(18064L), Some(246L), None, None) ), 2020-04-10 -> Map("App X" -> CacheStats(Some(1879), None, Some(864L), None, Some(5082L), None)) ) */ val stats1: Map[LocalDate, Map[String, CacheStats]] = Map( LocalDate.of(2020, 1, 18) -> Map( "App X" -> CacheStats.make(Some(1000), Some(5000), Some(2000), Some(500), Some(5000), Some(2500)), "App Y" -> CacheStats.make(None, Some(3500), Some(3100), None, Some(7890), Some(1513)), "App Z" -> CacheStats.make(None, None, Some(500), None, Some(800), None) ), LocalDate.of(2020, 1, 19) -> Map( "App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None), "App B" -> CacheStats.make(Some(1567), Some(2854), Some(4098), Some(1000), Some(5418), Some(3000)), "App C" -> CacheStats.make(None, None, Some(500), None, Some(800), None) ), LocalDate.of(2020, 3, 5) -> Map( "App A" -> CacheStats.make(Some(4378), Some(1000), Some(3210), Some(1000), None, None), "App Y" -> CacheStats.make(None, None, Some(9032), Some(123), None, None) ), LocalDate.of(2020, 4, 10) -> Map( "App X" -> CacheStats.make(Some(1879), None, Some(432), None, Some(2541), None) ) ) val stats2: Map[LocalDate, Map[String, CacheStats]] = Map( LocalDate.of(2020, 1, 18) -> Map( "App X" -> CacheStats.make(None, Some(5000), Some(2000), Some(500), Some(5000), Some(2500)), "App Y" -> CacheStats.make(Some(800), None, Some(3100), None, Some(7890), Some(1513)), "App Z" -> CacheStats.make(None, Some(678), None, None, Some(800), None) ), LocalDate.of(2020, 1, 19) -> Map( "App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None), "App B" -> CacheStats.make(Some(1567), None, Some(4098), Some(1000), Some(5418), Some(3000)), "App C" -> CacheStats.make(None, None, Some(500), Some(467), Some(800), None) ), LocalDate.of(2020, 3, 5) -> Map( "App A" -> CacheStats.make(Some(4378), None, Some(3210), Some(1000), None, None), "App Y" -> CacheStats.make(None, Some(1345), Some(9032), Some(123), None, None) ), LocalDate.of(2020, 4, 10) -> Map( "App X" -> CacheStats.make(None, None, Some(432), None, Some(2541), None) ) )
  • 16. Example 2 Let's suppose we have a List of cache stats data, and we want to sum all stats.
  • 17. Solution final case class CacheStats( entryCount: Option[Int], memorySize: Option[Long], hits: Option[Long], misses: Option[Long], loads: Option[Long], evictions: Option[Long] ) { self => def combine(that: CacheStats): CacheStats = { def combineCounters[A: Numeric](left: Option[A], right: Option[A]): Option[A] = (left, right) match { case (Some(l), Some(r)) => Some(implicitly[Numeric[A]].plus(l, r)) case (Some(l), None) => Some(l) case (None, Some(r)) => Some(r) case (None, None) => None } CacheStats( combineCounters(self.entryCount, that.entryCount), combineCounters(self.memorySize, that.memorySize), combineCounters(self.hits, that.hits), combineCounters(self.misses, that.misses), combineCounters(self.loads, that.loads), combineCounters(self.evictions, that.evictions) ) } }
  • 18. Solution def sum(cacheStats: List[CacheStats]): CacheStats = cacheStats.foldRight(CacheStats.make(None, None, None, None, None, None))(_ combine _) def main(args: Array[String]): Unit = { val cacheStats1 = List( CacheStats.make(Some(1000), Some(5000), Some(2000), Some(500), Some(5000), Some(2500)), CacheStats.make(None, Some(3500), Some(3100), None, Some(7890), Some(1513)), CacheStats.make(None, None, Some(500), None, Some(800), None) ) val cacheStats2 = List.empty[CacheStats] println(sum(cacheStats1)) // CacheStats(Some(1000), Some(8500L), Some(5600L), Some(500L), Some(13690L), Some(4013L)) println(sum(cacheStats2)) // CacheStats(None, None, None, None, None, None) }
  • 19. Example 3 Let's suppose we have several Options and we want to combine them into an Option of a Tuple
  • 20. Solution val option1 = Option(1) val option2 = Option(2) val option3 = Option(3) val option4 = Option(4) val option5 = Option(5) val option6 = Option(6) val option7 = Option(7) val option8 = Option(8) val option9 = Option(9) val option10 = Option(10) println { option1 .zip(option2) .zip(option3) .zip(option4) .zip(option5) .zip(option6) .zip(option7) .zip(option8) .zip(option9) .zip(option10) .headOption .map { case (((((((((a, b), c), d), e), f), g), h), i), j) => (a, b, c, d, e, f, g, h, i, j) } } // Some(Tuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
  • 21. Example 4 Let's suppose we request some Person data from three different servers, and we want to return the first successful response, or a failure if all the requests fail
  • 22. Solution import com.twitter.util.{ Return, Throw, Try } final case class Person( firstName: String, lastName: String, country: String, state: String, age: Int ) def getPerson(url: String): Try[Person] = if (url.contains("1") || url.contains("2")) Throw(new Exception("Server error")) else Return(Person("Ana", "Perez", "Bolivia", "La Paz", 30)) lazy val response1 = getPerson("http://server1.com/info") lazy val response2 = getPerson("http://server2.com/info") lazy val response3 = getPerson("http://server3.com/info") val response: Try[Person] = response1.rescue { case _ => response2.rescue { case _ => response3 } } println(response) // Return(Person(Ana,Perez,Bolivia,La Paz,30))
  • 24. Example 5 Obtain the following information from a Binary Tree of Integers: ● The minimum value ● The maximum value ● The number of elements ● The number of elements greater than 5 ● Does it contain the number 20? ● Does it contain negative numbers? ● Are all elements positive numbers? ● What’s the first element greater than 5? ● Is the tree empty? ● Is the tree non empty? ● What’s the sum of the elements? ● What’s the product of the elements? ● What’s the reversed tree?
  • 25. Solution sealed trait BinaryTree[+A] object BinaryTree { private final case class Leaf[A](value: A) extends BinaryTree[A] private final case class Branch[A](left: BinaryTree[A], right: BinaryTree[A]) extends BinaryTree[A] def leaf[A](value: A): BinaryTree[A] = BinaryTree.Leaf(value) def branch[A](left: BinaryTree[A], right: BinaryTree[A]): BinaryTree[A] = BinaryTree.Branch(left, right) }
  • 26. Solution sealed trait BinaryTree[+A] { self => import BinaryTree._ def contains[A1 >: A](a: A1): Boolean = self.count(_ == a) > 0 def count(f: A => Boolean): Int = self match { case Leaf(a) => if (f(a)) 1 else 0 case Branch(l, r) => l.count(f) + r.count(f) } def exists(f: A => Boolean): Boolean = self.count(f) > 0 def find(f: A => Boolean): Option[A] = self match { case Leaf(a) => Some(a).filter(f) case Branch(l, r) => l.find(f).orElse(r.find(f)) } def fold[A1 >: A](f: (A1, A1) => A1): A1 = self match { case Leaf(a) => a case Branch(left, right) => f(left.fold(f), right.fold(f)) } def forall(f: A => Boolean): Boolean = self.count(f) == self.size def isEmpty: Boolean = self.size == 0 ... }
  • 27. Solution sealed trait BinaryTree[+A] { self => import BinaryTree._ ... def map[B](f: A => B): BinaryTree[B] = self match { case Leaf(a) => Leaf(f(a)) case Branch(left, right) => Branch(left.map(f), right.map(f)) } def max[A1 >: A](implicit ordering: Ordering[A1]): A1 = self.maxBy(identity[A1]) def maxBy[B](f: A => B)(implicit ordering: Ordering[B]): A = self.fold(Ordering.by(f).max) def min[A1 >: A](implicit ordering: Ordering[A1]): A1 = self.minBy(identity[A1]) def minBy[B](f: A => B)(implicit ordering: Ordering[B]): A = self.fold(Ordering.by(f).min) def nonEmpty: Boolean = !self.isEmpty def product[A1 >: A](implicit numeric: Numeric[A1]): A1 = self.fold(numeric.times) def reverse: BinaryTree[A] = self match { case Leaf(a) => Leaf(a) case Branch(l, r) => Branch(r.reverse, l.reverse) } def sum[A1 >: A](implicit numeric: Numeric[A1]): A1 = self.fold(numeric.plus) def size: Int = self.count(_ => true) }
  • 28. val tree = branch( branch( branch( leaf(5), leaf(10) ), branch( leaf(7), leaf(8) ) ), branch( branch( leaf(1), leaf(11) ), branch( leaf(17), leaf(21) ) ) ) Solution println(s"Minimum: ${tree.min}") println(s"Maximum: ${tree.max}") println(s"Number of elements: ${tree.size}") println(s"Number of elements greater than 5: ${tree.count(_ > 5)}") println(s"Does the tree contain the number 20?: ${tree.contains(20)}") println(s"Does the tree contain negative numbers?: ${tree.exists(_ < 0)}") println(s"Are all elements in the tree positive numbers?: ${tree.forall(_ > 0)}") println(s"What's the first element greater than 5?: ${tree.find(_ > 5)}") println(s"Is the tree empty?: ${tree.isEmpty}") println(s"Is the tree non empty?: ${tree.nonEmpty}") println(s"What's the sum of the elements (1st approach)?: ${tree.fold(_ + _)}") println(s"What's the sum of the elements (2nd approach)?: ${tree.sum}") println(s"What's the product of the elements?: ${tree.product}") pprint.pprintln(s"Reversed tree: ${tree.reverse}")
  • 30. Example 6 Obtain the following information from a Binary Tree of Person: ● Who’s the youngest person? ● Who’s the oldest person? ● What’s the average age? ● How many people are there per location?
  • 31. Solution sealed trait BinaryTree[+A] { self => import BinaryTree._ ... def groupBy[K](f: A => K): Map[K, List[A]] = self match { case Leaf(a) => Map(f(a) -> List(a)) case Branch(l, r) => val leftMap = l.groupBy(f) val rightMap = r.groupBy(f) (leftMap.keySet ++ rightMap.keySet).map { key => (leftMap.get(key), rightMap.get(key)) match { case (Some(as1), Some(as2)) => key -> (as1 ++ as2) case (Some(as1), None) => key -> as1 case (None, Some(as2)) => key -> as2 case _ => throw new Error("Boom!") } }.toMap } ... }
  • 32. Solution val tree = branch( branch( branch( leaf(Person("Adam", "Peterson", "USA", "California", 40)), leaf(Person("Rachel", "Johns", "USA", "Los Angeles", 20)) ), branch( leaf(Person("Jose", "Perez", "Bolivia", "La Paz", 35)), leaf(Person("Monica", "Simpson", "UK", "London", 65)) ) ), branch( branch( leaf(Person("David", "Johnson", "USA", "California", 32)), leaf(Person("Ana", "Sanchez", "Bolivia", "La Paz", 27)) ), branch( leaf(Person("Laura", "Adams", "UK", "London", 54)), leaf(Person("Roberto", "Mendes", "Brazil", "Minas Gerais", 24)) ) ) ) println(s"Youngest person: ${tree.minBy(_.age)}") println(s"Oldest person: ${tree.maxBy(_.age)}") println(s"What's the average age?: ${tree.map(_.age).sum / tree.size}") println( s"How many people are there per location?: ${tree.groupBy(person => (person.country, person.state)).mapValues(_.length)}" )
  • 33. Example 7 Process a Binary Tree of Strings, sending them to a server which just echoes the received messages (encapsulated inside Future), and return a Future of a Binary Tree containing the responses.
  • 34. Solution sealed trait BinaryTree[+A] { self => import BinaryTree._ ... def foreachFuture[B](f: A => Future[B]): Future[BinaryTree[B]] = self match { case Leaf(a) => f(a).map(Leaf(_)) case Branch(l, r) => l.foreachFuture(f).zipWith(r.foreachFuture(f))(Branch(_, _)) } ... }
  • 35. Solution def echo(message: String): Future[String] = Future { Thread.sleep(1000) s"Echo: $message" } val messages = branch( branch( leaf("message 1"), leaf("message 2") ), branch( branch( leaf("message 3"), leaf("message 4") ), branch( leaf("message 5"), leaf("message 6") ) ) ) val responses = messages.foreachFuture(echo) pprint.pprintln(Await.result(responses, 5.seconds)) /* Branch( Branch(Leaf("Echo: message 1"), Leaf("Echo: message 2")), Branch( Branch(Leaf("Echo: message 3"), Leaf("Echo: message 4")), Branch(Leaf("Echo: message 5"), Leaf("Echo: message 6")) ) ) */
  • 38.
  • 40. What is ZIO Prelude? Scala-first take on Functional Abstractions
  • 41. ZIO Prelude gives us... Data types that complement the Scala Standard Library: ● NonEmptyList ● NonEmptySet ● ZSet ● ZNonEmptySet ● Validation ● ZPure
  • 42. ZIO Prelude gives us... Newtypes that allow to increase type safety in domain modeling, wrapping an existing type without adding any runtime overhead.
  • 43. ZIO Prelude gives us... Typeclasses to describe similarities across different types, so we can eliminate duplication/boilerplate: ● Business entities (Person, ShoppingCart, etc.) ● Effect-like structures (Try, Option, Future, Either, etc.) ● Collection-like structures (List, Tree, etc.)
  • 45. Example 1 Let's suppose we have some cache stats data, organized by date and application. This data comes from two sources, and we need to combine them into one, by summing counters when collisions exist.
  • 46. Solution: Associative Typeclass trait Associative[A] { def combine(l: => A, r: => A): A } // Associativity law (a1 <> (a2 <> a3)) <-> ((a1 <> a2) <> a3)
  • 47. Solution: Associative Typeclass ZIO Prelude has Associativeinstances for Scala Standard Types: ● Boolean ● Byte ● Char ● Short ● Int ● Long ● Float ● Double ● String ● Option ● Vector ● List ● Set ● Tuple
  • 48. Solution: Associative Typeclass And, of course, we can create instances of Associative for our own types (or types on third party libraries)!
  • 49. Solution: Associative Typeclass final case class CacheStats( entryCount: Option[Sum[Int]], memorySize: Option[Sum[Long]], hits: Option[Sum[Long]], misses: Option[Sum[Long]], loads: Option[Sum[Long]], evictions: Option[Sum[Long]] ) import zio.prelude._ object CacheStats { def make( entryCount: Option[Int], memorySize: Option[Long], hits: Option[Long], misses: Option[Long], loads: Option[Long], evictions: Option[Long] ): CacheStats = CacheStats( entryCount.map(Sum(_)), memorySize.map(Sum(_)), hits.map(Sum(_)), misses.map(Sum(_)), loads.map(Sum(_)), evictions.map(Sum(_)) ) implicit val associative = Associative.make[CacheStats] { (l, r) => CacheStats( l.entryCount <> r.entryCount, l.memorySize <> r.memorySize, l.hits <> r.hits, l.misses <> r.misses, l.loads <> r.loads, l.evictions <> r.evictions ) } }
  • 51. Solution: Associative Typeclass ● All typeclasses in ZIO Prelude include a set of laws that instances must obey. ● We can use ZIO Test to check that laws of a typeclass are fulfilled by a given instance, using Property Based Testing.
  • 52. Solution: Associative Typeclass object CacheStatsSpec extends DefaultRunnableSpec { def spec = suite("CacheStatsSpec")( suite("CacheStats")( testM("associative")(checkAllLaws(Associative)(cacheStatsGen)) ) ) def cacheStatsGen[R <: Random with Sized]: Gen[R, CacheStats] = { val intGen = Gen.oneOf(Gen.none, Gen.anyInt.map(Sum(_)).map(Some(_))) val longGen = Gen.oneOf(Gen.none, Gen.anyLong.map(Sum(_)).map(Some(_))) for { entryCount <- intGen memorySize <- longGen hits <- longGen misses <- longGen loads <- longGen evictions <- longGen } yield CacheStats(entryCount, memorySize, hits, misses, loads, evictions) } }
  • 53. Solution: Associative Typeclass import zio.prelude._ pprint.pprintln( Associative[Map[LocalDate, Map[String, CacheStats]]].combine( stats1, stats2 ) ) /* Map( 2020-01-18 -> Map( "App X" -> CacheStats(Some(1000), Some(10000L), Some(4000L), Some(1000L), Some(10000L), Some(5000L)), "App Y" -> CacheStats(Some(800), Some(3500L), Some(6200L), None, Some(15780L), Some(3026L)), "App Z" -> CacheStats(None, Some(678L), Some(500L), None, Some(1600L), None) ), 2020-01-19 -> Map( "App A" -> CacheStats(None, None, Some(8196L), None, Some(10836L), None), "App B" -> CacheStats(Some(3134), Some(2854L), Some(8196L), Some(2000L), Some(10836L), Some(6000L)), "App C" -> CacheStats(None, None, Some(1000L), Some(467L), Some(1600L), None) ), 2020-03-05 -> Map( "App A" -> CacheStats(Some(8756), Some(1000L), Some(6420L), Some(2000L), None, None), "App Y" -> CacheStats(None, Some(1345L), Some(18064L), Some(246L), None, None) ), 2020-04-10 -> Map("App X" -> CacheStats(Some(1879), None, Some(864L), None, Some(5082L), None)) ) */ val stats1: Map[LocalDate, Map[String, CacheStats]] = Map( LocalDate.of(2020, 1, 18) -> Map( "App X" -> CacheStats.make(Some(1000), Some(5000), Some(2000), Some(500), Some(5000), Some(2500)), "App Y" -> CacheStats.make(None, Some(3500), Some(3100), None, Some(7890), Some(1513)), "App Z" -> CacheStats.make(None, None, Some(500), None, Some(800), None) ), LocalDate.of(2020, 1, 19) -> Map( "App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None), "App B" -> CacheStats.make(Some(1567), Some(2854), Some(4098), Some(1000), Some(5418), Some(3000)), "App C" -> CacheStats.make(None, None, Some(500), None, Some(800), None) ), LocalDate.of(2020, 3, 5) -> Map( "App A" -> CacheStats.make(Some(4378), Some(1000), Some(3210), Some(1000), None, None), "App Y" -> CacheStats.make(None, None, Some(9032), Some(123), None, None) ), LocalDate.of(2020, 4, 10) -> Map( "App X" -> CacheStats.make(Some(1879), None, Some(432), None, Some(2541), None) ) ) val stats2: Map[LocalDate, Map[String, CacheStats]] = Map( LocalDate.of(2020, 1, 18) -> Map( "App X" -> CacheStats.make(None, Some(5000), Some(2000), Some(500), Some(5000), Some(2500)), "App Y" -> CacheStats.make(Some(800), None, Some(3100), None, Some(7890), Some(1513)), "App Z" -> CacheStats.make(None, Some(678), None, None, Some(800), None) ), LocalDate.of(2020, 1, 19) -> Map( "App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None), "App B" -> CacheStats.make(Some(1567), None, Some(4098), Some(1000), Some(5418), Some(3000)), "App C" -> CacheStats.make(None, None, Some(500), Some(467), Some(800), None) ), LocalDate.of(2020, 3, 5) -> Map( "App A" -> CacheStats.make(Some(4378), None, Some(3210), Some(1000), None, None), "App Y" -> CacheStats.make(None, Some(1345), Some(9032), Some(123), None, None) ), LocalDate.of(2020, 4, 10) -> Map( "App X" -> CacheStats.make(None, None, Some(432), None, Some(2541), None) ) )
  • 54. Solution: Associative Typeclass import zio.prelude._ pprint.pprintln(stats1 <> stats2) /* Map( 2020-01-18 -> Map( "App X" -> CacheStats(Some(1000), Some(10000L), Some(4000L), Some(1000L), Some(10000L), Some(5000L)), "App Y" -> CacheStats(Some(800), Some(3500L), Some(6200L), None, Some(15780L), Some(3026L)), "App Z" -> CacheStats(None, Some(678L), Some(500L), None, Some(1600L), None) ), 2020-01-19 -> Map( "App A" -> CacheStats(None, None, Some(8196L), None, Some(10836L), None), "App B" -> CacheStats(Some(3134), Some(2854L), Some(8196L), Some(2000L), Some(10836L), Some(6000L)), "App C" -> CacheStats(None, None, Some(1000L), Some(467L), Some(1600L), None) ), 2020-03-05 -> Map( "App A" -> CacheStats(Some(8756), Some(1000L), Some(6420L), Some(2000L), None, None), "App Y" -> CacheStats(None, Some(1345L), Some(18064L), Some(246L), None, None) ), 2020-04-10 -> Map("App X" -> CacheStats(Some(1879), None, Some(864L), None, Some(5082L), None)) ) */ val stats1: Map[LocalDate, Map[String, CacheStats]] = Map( LocalDate.of(2020, 1, 18) -> Map( "App X" -> CacheStats.make(Some(1000), Some(5000), Some(2000), Some(500), Some(5000), Some(2500)), "App Y" -> CacheStats.make(None, Some(3500), Some(3100), None, Some(7890), Some(1513)), "App Z" -> CacheStats.make(None, None, Some(500), None, Some(800), None) ), LocalDate.of(2020, 1, 19) -> Map( "App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None), "App B" -> CacheStats.make(Some(1567), Some(2854), Some(4098), Some(1000), Some(5418), Some(3000)), "App C" -> CacheStats.make(None, None, Some(500), None, Some(800), None) ), LocalDate.of(2020, 3, 5) -> Map( "App A" -> CacheStats.make(Some(4378), Some(1000), Some(3210), Some(1000), None, None), "App Y" -> CacheStats.make(None, None, Some(9032), Some(123), None, None) ), LocalDate.of(2020, 4, 10) -> Map( "App X" -> CacheStats.make(Some(1879), None, Some(432), None, Some(2541), None) ) ) val stats2: Map[LocalDate, Map[String, CacheStats]] = Map( LocalDate.of(2020, 1, 18) -> Map( "App X" -> CacheStats.make(None, Some(5000), Some(2000), Some(500), Some(5000), Some(2500)), "App Y" -> CacheStats.make(Some(800), None, Some(3100), None, Some(7890), Some(1513)), "App Z" -> CacheStats.make(None, Some(678), None, None, Some(800), None) ), LocalDate.of(2020, 1, 19) -> Map( "App A" -> CacheStats.make(None, None, Some(4098), None, Some(5418), None), "App B" -> CacheStats.make(Some(1567), None, Some(4098), Some(1000), Some(5418), Some(3000)), "App C" -> CacheStats.make(None, None, Some(500), Some(467), Some(800), None) ), LocalDate.of(2020, 3, 5) -> Map( "App A" -> CacheStats.make(Some(4378), None, Some(3210), Some(1000), None, None), "App Y" -> CacheStats.make(None, Some(1345), Some(9032), Some(123), None, None) ), LocalDate.of(2020, 4, 10) -> Map( "App X" -> CacheStats.make(None, None, Some(432), None, Some(2541), None) ) )
  • 56. Example 2 Let's suppose we have a List of cache stats data, and we want to sum all stats.
  • 57. Solution: Identity Typeclass trait Identity[A] extends Associative[A] { def identity: A } // Associativity law (a1 <> (a2 <> a3)) <-> ((a1 <> a2) <> a3) // Left Identity law (identity <> a) <-> a // Right Identity law (a <> identity) <-> a
  • 58. Solution: Identity Typeclass final case class CacheStats( entryCount: Option[Sum[Int]], memorySize: Option[Sum[Long]], hits: Option[Sum[Long]], misses: Option[Sum[Long]], loads: Option[Sum[Long]], evictions: Option[Sum[Long]] ) import zio.prelude._ object CacheStats { def make( entryCount: Option[Int], memorySize: Option[Long], hits: Option[Long], misses: Option[Long], loads: Option[Long], evictions: Option[Long] ): CacheStats = CacheStats( entryCount.map(Sum(_)), memorySize.map(Sum(_)), hits.map(Sum(_)), misses.map(Sum(_)), loads.map(Sum(_)), evictions.map(Sum(_)) ) implicit val identity = Identity.make[CacheStats]( CacheStats.make(None, None, None, None, None, None), (l, r) => CacheStats( l.entryCount <> r.entryCount, l.memorySize <> r.memorySize, l.hits <> r.hits, l.misses <> r.misses, l.loads <> r.loads, l.evictions <> r.evictions ) ) }
  • 59. Solution: Identity Typeclass def sum(cacheStats: List[CacheStats]): CacheStats = cacheStats.foldRight(Identity[CacheStats].identity)(_ <> _) def main(args: Array[String]): Unit = { val cacheStats1 = List( CacheStats.make(Some(1000), Some(5000), Some(2000), Some(500), Some(5000), Some(2500)), CacheStats.make(None, Some(3500), Some(3100), None, Some(7890), Some(1513)), CacheStats.make(None, None, Some(500), None, Some(800), None) ) val cacheStats2 = List.empty[CacheStats] println(sum(cacheStats1)) // CacheStats(Some(1000), Some(8500L), Some(5600L), Some(500L), Some(13690L), Some(4013L)) println(sum(cacheStats2)) // CacheStats(None, None, None, None, None, None) }
  • 61. Example 3 Let's suppose we have several Options and we want to combine them into an Option of a Tuple
  • 62. Solution: AssociativeBoth Typeclass trait AssociativeBoth[F[_]] { def both[A, B](fa: => F[A], fb: => F[B]): F[(A, B)] } // Associativity law both(fa, both(fb, fc)) ~ both(both(fa, fb), fc)
  • 63. Solution: AssociativeBoth Typeclass ZIO Prelude has AssociativeBothinstances for Scala Standard Types: ● Either ● Future ● List ● Option ● Try ● Vector
  • 64. Solution: AssociativeBoth Typeclass import zio.prelude._ def main(args: Array[String]): Unit = { val option1 = Option(1) val option2 = Option(2) val option3 = Option(3) val option4 = Option(4) val option5 = Option(5) val option6 = Option(6) val option7 = Option(7) val option8 = Option(8) val option9 = Option(9) val option10 = Option(10) println { AssociativeBoth.tupleN(option1, option2, option3, option4, option5, option6, option7, option8, option9, option10) } // Some(Tuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) }
  • 66. Example 4 Let's suppose we request some Person data from three different servers, and we want to return the first successful response, or a failure if all the requests fail
  • 67. Solution: AssociativeEither Typeclass trait AssociativeEither[F[_]] { def either[A, B](fa: => F[A], fb: => F[B]): F[Either[A, B]] } // Associativity law either(fa, either(fb, fc)) ~ either(either(fa, fb), fc)
  • 68. Solution: AssociativeEither Typeclass import com.twitter.util.{ Return, Throw, Try } import zio.prelude._ implicit val TryAssociativeEither = new AssociativeEither[Try] { def either[A, B](fa: => Try[A], fb: => Try[B]): Try[Either[A, B]] = fa.map(Left(_)) rescue { case _ => fb.map(Right(_)) } }
  • 69. Solution: AssociativeEither Typeclass import com.twitter.util.{ Return, Throw, Try } import zio.prelude._ final case class Person(firstName: String, lastName: String, country: String, state: String, age: Int) def getPerson(url: String): Try[Person] = if (url.contains("1") || url.contains("2")) Throw(new Exception("Server error")) else Return(Person("Ana", "Perez", "Bolivia", "La Paz", 30)) def main(args: Array[String]): Unit = { lazy val response1 = getPerson("http://server1.com/info") lazy val response2 = getPerson("http://server2.com/info") lazy val response3 = getPerson("http://server3.com/info") val response: Try[Person] = response1 orElse response2 orElse response3 println(response) // Return(Person("Ana", "Perez", "Bolivia", "La Paz", 30)) }
  • 71. Example 5 Obtain the following information from a Binary Tree of Integers: ● The minimum value ● The maximum value ● The number of elements ● The number of elements greater than 5 ● Does it contain the number 20? ● Does it contain negative numbers? ● Are all elements positive numbers? ● What’s the first element greater than 5? ● Is the tree empty? ● Is the tree non empty? ● What’s the sum of the elements? ● What’s the product of the elements? ● What’s the reversed tree?
  • 72. Solution: Traversable Typeclass trait Traversable[F[+_]] extends Covariant[F] { def foreach[G[+_]: IdentityBoth: Covariant, A, B](fa: F[A])(f: A => G[B]): G[F[B]] // A lot of methods for free! def contains[A, A1 >: A](fa: F[A])(a: A1)(implicit A: Equal[A1]): Boolean def count[A](fa: F[A])(f: A => Boolean): Int def exists[A](fa: F[A])(f: A => Boolean): Boolean def find[A](fa: F[A])(f: A => Boolean): Option[A] def flip[G[+_]: IdentityBoth: Covariant, A](fa: F[G[A]]): G[F[A]] def fold[A: Identity](fa: F[A]): A def foldLeft[S, A](fa: F[A])(s: S)(f: (S, A) => S): S def foldMap[A, B: Identity](fa: F[A])(f: A => B): B def foldRight[S, A](fa: F[A])(s: S)(f: (A, S) => S): S def forall[A](fa: F[A])(f: A => Boolean): Boolean def foreach_[G[+_]: IdentityBoth: Covariant, A](fa: F[A])(f: A => G[Any]): G[Unit] def isEmpty[A](fa: F[A]): Boolean def map[A, B](f: A => B): F[A] => F[B] def mapAccum[S, A, B](fa: F[A])(s: S)(f: (S, A) => (S, B)): (S, F[B]) def maxOption[A: Ord](fa: F[A]): Option[A] def maxByOption[A, B: Ord](fa: F[A])(f: A => B): Option[A] def minOption[A: Ord](fa: F[A]): Option[A] def minByOption[A, B: Ord](fa: F[A])(f: A => B): Option[A] def nonEmpty[A](fa: F[A]): Boolean def product[A](fa: F[A])(implicit ev: Identity[Prod[A]]): A def reduceMapOption[A, B: Associative](fa: F[A])(f: A => B): Option[B] def reduceOption[A](fa: F[A])(f: (A, A) => A): Option[A] def reverse[A](fa: F[A]): F[A] def size[A](fa: F[A]): Int def sum[A](fa: F[A])(implicit ev: Identity[Sum[A]]): A def toChunk[A](fa: F[A]): Chunk[A] def toList[A](fa: F[A]): List[A] def zipWithIndex[A](fa: F[A]): F[(A, Int)] }
  • 73. Solution: Traversable Typeclass sealed trait BinaryTree[+A] { self => import BinaryTree._ def map[B](f: A => B): BinaryTree[B] = self match { case Leaf(a) => Leaf(f(a)) case Branch(left, right) => Branch(left.map(f), right.map(f)) } } object BinaryTree { private final case class Leaf[A](value: A) extends BinaryTree[A] private final case class Branch[A](left: BinaryTree[A], right: BinaryTree[A]) extends BinaryTree[A] def leaf[A](value: A): BinaryTree[A] = BinaryTree.Leaf(value) def branch[A](left: BinaryTree[A], right: BinaryTree[A]): BinaryTree[A] = BinaryTree.Branch(left, right) implicit val traversable = new Traversable[BinaryTree] { def foreach[G[+ _]: IdentityBoth: Covariant, A, B](fa: BinaryTree[A])(f: A => G[B]): G[BinaryTree[B]] = fa match { case Leaf(a) => f(a).map(Leaf(_)) case Branch(l, r) => foreach(l)(f).zipWith(foreach(r)(f))(Branch(_, _)) } override def map[A, B](f: A => B): BinaryTree[A] => BinaryTree[B] = _.map(f) } }
  • 74. val tree = branch( branch( branch( leaf(5), leaf(10) ), branch( leaf(7), leaf(8) ) ), branch( branch( leaf(1), leaf(11) ), branch( leaf(17), leaf(21) ) ) ) Solution: Traversable Typeclass println(s"Minimum: ${tree.minOption}") println(s"Maximum: ${tree.maxOption}") println(s"Number of elements: ${tree.size}") println(s"Number of elements greater than 5: ${tree.count(_ > 5)}") println(s"Does the tree contain the number 20?: ${tree.contains(20)}") println(s"Does the tree contain negative numbers?: ${tree.exists(_ < 0)}") println(s"Are all elements in the tree positive numbers?: ${tree.forall(_ > 0)}") println(s"What's the first element greater than 5?: ${tree.find(_ > 5)}") println(s"Is the tree empty?: ${tree.isEmpty}") println(s"Is the tree non empty?: ${tree.nonEmpty}") println(s"What's the sum of the elements: ${tree.sum}") println(s"What's the product of the elements?: ${tree.product}") pprint.pprintln(s"Reversed tree: ${tree.reverse}")
  • 76. Example 6 Obtain the following information from a Binary Tree of Person: ● Who’s the youngest person? ● Who’s the oldest person? ● What’s the average age? ● How many people are there per location?
  • 77. Solution: Traversable Typeclass val tree = branch( branch( branch( leaf(Person("Adam", "Peterson", "USA", "California", 40)), leaf(Person("Rachel", "Johns", "USA", "Los Angeles", 20)) ), branch( leaf(Person("Jose", "Perez", "Bolivia", "La Paz", 35)), leaf(Person("Monica", "Simpson", "UK", "London", 65)) ) ), branch( branch( leaf(Person("David", "Johnson", "USA", "California", 32)), leaf(Person("Ana", "Sanchez", "Bolivia", "La Paz", 27)) ), branch( leaf(Person("Laura", "Adams", "UK", "London", 54)), leaf(Person("Roberto", "Mendes", "Brazil", "Minas Gerais", 24)) ) ) ) println(s"Youngest person: ${tree.minByOption(_.age)}") println(s"Oldest person: ${tree.maxByOption(_.age)}") println(s"What's the average age?: ${tree.map(_.age).sum / tree.size}") println( s"How many people are there per location?: ${Traversable[BinaryTree].groupBy(tree)(person => (person.country, person.state)).mapValues(_.length)}" )
  • 78.
  • 79. Solution: NonEmptyTraversable Typeclass trait NonEmptyTraversable[F[+_]] extends Traversable[F] { def foreach1[G[+_]: AssociativeBoth: Covariant, A, B](fa: F[A])(f: A => G[B]): G[F[B]] def flip1[G[+_]: AssociativeBoth: Covariant, A](fa: F[G[A]]): G[F[A]] override def foreach[G[+_]: IdentityBoth: Covariant, A, B](fa: F[A])(f: A => G[B]): G[F[B]] def foreach1_[G[+_]: AssociativeBoth: Covariant, A](fa: F[A])(f: A => G[Any]): G[Unit] def max[A: Ord](fa: F[A]): A def maxBy[A, B: Ord](fa: F[A])(f: A => B): A def min[A: Ord](fa: F[A]): A def minBy[A, B: Ord](fa: F[A])(f: A => B): A def reduce[A](fa: F[A])(f: (A, A) => A): A def reduce1[A: Associative](fa: F[A]): A def reduceMap[A, B: Associative](fa: F[A])(f: A => B): B def reduceMapLeft[A, B](fa: F[A])(map: A => B)(reduce: (B, A) => B): B def reduceMapRight[A, B](fa: F[A])(map: A => B)(reduce: (A, B) => B): B def toNonEmptyChunk[A](fa: F[A]): NonEmptyChunk[A] def toNonEmptyList[A](fa: F[A]): NonEmptyList[A] }
  • 80. Solution: NonEmptyTraversable Typeclass sealed trait BinaryTree[+A] { self => import BinaryTree._ def map[B](f: A => B): BinaryTree[B] = self match { case Leaf(a) => Leaf(f(a)) case Branch(left, right) => Branch(left.map(f), right.map(f)) } } object BinaryTree { private final case class Leaf[A](value: A) extends BinaryTree[A] private final case class Branch[A](left: BinaryTree[A], right: BinaryTree[A]) extends BinaryTree[A] def leaf[A](value: A): BinaryTree[A] = BinaryTree.Leaf(value) def branch[A](left: BinaryTree[A], right: BinaryTree[A]): BinaryTree[A] = BinaryTree.Branch(left, right) implicit val nonEmptyTraversable = new NonEmptyTraversable[BinaryTree] { def foreach1[G[+ _]: AssociativeBoth: Covariant, A, B](fa: BinaryTree[A])(f: A => G[B]): G[BinaryTree[B]] = fa match { case Leaf(a) => f(a).map(Leaf(_)) case Branch(l, r) => foreach1(l)(f).zipWith(foreach1(r)(f))(Branch(_, _)) } } }
  • 81. Solution: NonEmptyTraversable Typeclass val tree = branch( branch( branch( leaf(Person("Adam", "Peterson", "USA", "California", 40)), leaf(Person("Rachel", "Johns", "USA", "Los Angeles", 20)) ), branch( leaf(Person("Jose", "Perez", "Bolivia", "La Paz", 35)), leaf(Person("Monica", "Simpson", "UK", "London", 65)) ) ), branch( branch( leaf(Person("David", "Johnson", "USA", "California", 32)), leaf(Person("Ana", "Sanchez", "Bolivia", "La Paz", 27)) ), branch( leaf(Person("Laura", "Adams", "UK", "London", 54)), leaf(Person("Roberto", "Mendes", "Brazil", "Minas Gerais", 24)) ) ) ) println(s"Youngest person: ${NonEmptyTraversable[BinaryTree].minBy(tree)(_.age)}") println(s"Oldest person: ${NonEmptyTraversable[BinaryTree].maxBy(tree)(_.age)}")
  • 83. Example 7 Process a Binary Tree of Strings, sending them to a server which just echoes the received messages (encapsulated inside Future), and return a Future of a Binary Tree containing the responses.
  • 84. Solution: Traversable Typeclass def echo(message: String): Future[String] = Future { Thread.sleep(1000) s"Echo: $message" } val messages = branch( branch( leaf("message 1"), leaf("message 2") ), branch( branch( leaf("message 3"), leaf("message 4") ), branch( leaf("message 5"), leaf("message 6") ) ) ) val responses = messages.foreach(echo) pprint.pprintln(Await.result(responses, 5.seconds)) /* Branch( Branch(Leaf("Echo: message 1"), Leaf("Echo: message 2")), Branch( Branch(Leaf("Echo: message 3"), Leaf("Echo: message 4")), Branch(Leaf("Echo: message 5"), Leaf("Echo: message 6")) ) ) */
  • 86. Summary ● There's a lot of boilerplate in our applications ● Abstraction is the key to eliminating boilerplate ● Functional code uses typeclasses for abstraction
  • 87. Summary ZIO Prelude has typeclasses that let you eliminate boilerplate across: ● Business entities: ○ Associative/Identity ● Effect-like structures: ○ AssociativeBoth/IdentityBoth ○ AssociativeEither/IdentityEither ● Collection-like structures: ○ Traversable/NonEmptyTraversable
  • 88. Special thanks ● To Functional Scala organizers for hosting this presentation ● To John De Goes for guidance and support
  • 90. Where to learn more ● ZIO Prelude on Github: https://github.com/zio/zio-prelude/ ● SF Scala: Reimagining Functional Type Classes, talk by John De Goes and Adam Fraser ● Functional World: Exploring ZIO Prelude - The game changer for typeclasses in Scala, talk by Jorge Vásquez