Functional
Programming 101 with
Scala and ZIO
Functional World
April 15th, 2021
Jorge Vásquez
Scala Developer
@Scalac
Agenda
Agenda
• Functional Programming (FP)
basic concepts
Agenda
• Functional Programming (FP)
basic concepts
• ZIO basic concepts
Agenda
• Functional Programming (FP)
basic concepts
• ZIO basic concepts
• Live coding: Tic-Tac-Toe game
What is Functional
Programming?
What is Functional Programming?
Programming paradigm, where programs are a
composition of pure functions
Characteristics of a
Pure Function
Characteristics of a
Pure Function
• Total
Characteristics of a
Pure Function
• Total
• Deterministic and depends on its
inputs only
Characteristics of a
Pure Function
• Total
• Deterministic and depends on its
inputs only
• Must not have side effects
A Pure Function must be Total
For each input that is provided to the function there must
be a defined output
A Pure Function must be Total
def divide(a: Int, b: Int): Int = a / b
divide(5, 0)
// java.lang.ArithmeticException: / by zero
A Pure Function must be Total
def divide(a: Int, b: Int): Int = a / b
divide(5, 0)
// java.lang.ArithmeticException: / by zero
A Pure Function must be Total
def divide(a: Int, b: Int): Int = a / b
divide(5, 0)
// java.lang.ArithmeticException: / by zero
A Pure Function must be Total
def divide(a: Int, b: Int): Int = a / b
divide(5, 0)
// java.lang.ArithmeticException: / by zero
A Pure Function must be Total
def divide(a: Int, b: Int): Int = a / b
A Pure Function must be Total
def divide(a: Int, b: Int): Int = a / b
• The signature of this function tells a lie!
A Pure Function must be Total
def divide(a: Int, b: Int): Int = a / b
• The signature of this function tells a lie!
• Every time we call it we will have to be very careful
A Pure Function must be Total
def divide(a: Int, b: Int): Int = a / b
• The signature of this function tells a lie!
• Every time we call it we will have to be very careful
• Runtime exceptions can happen. The compiler is not
able to do anything to help us to avoid this
A Pure Function must be Total
def divide(a: Int, b: Int): Option[Int] =
if (b != 0) Some(a / b) else None
divide(5, 0)
// None
A Pure Function must be Total
def divide(a: Int, b: Int): Option[Int] =
if (b != 0) Some(a / b) else None
divide(5, 0)
// None
A Pure Function must be Total
def divide(a: Int, b: Int): Option[Int] =
if (b != 0) Some(a / b) else None
divide(5, 0)
// None
A Pure Function must be Total
def divide(a: Int, b: Int): Option[Int] =
if (b != 0) Some(a / b) else None
divide(5, 0)
// None
A Pure Function must be Total
def divide(a: Int, b: Int): Option[Int] =
if (b != 0) Some(a / b) else None
A Pure Function must be Total
def divide(a: Int, b: Int): Option[Int] =
if (b != 0) Some(a / b) else None
• The function’s signature clearly communicates that some
inputs are not handled
A Pure Function must be Total
def divide(a: Int, b: Int): Option[Int] =
if (b != 0) Some(a / b) else None
• The function’s signature clearly communicates that some
inputs are not handled
• The compiler will force us to consider the case in which the
result is not defined
A Pure Function must be Total
def divide(a: Int, b: Int): Option[Int] =
if (b != 0) Some(a / b) else None
• The function’s signature clearly communicates that some
inputs are not handled
• The compiler will force us to consider the case in which the
result is not defined
• No runtime exceptions!
A Pure Function must be Deterministic
and depend only on its inputs
For each input that is provided to the function, the same
output must be returned, no matter how many times the
function is called
A Pure Function must be Deterministic
and depend only on its inputs
def generateRandomInt(): Int = (new scala.util.Random).nextInt
generateRandomInt() // Result: -272770531
generateRandomInt() // Result: 217937820
A Pure Function must be Deterministic
and depend only on its inputs
def generateRandomInt(): Int = (new scala.util.Random).nextInt
generateRandomInt() // Result: -272770531
generateRandomInt() // Result: 217937820
A Pure Function must be Deterministic
and depend only on its inputs
def generateRandomInt(): Int = (new scala.util.Random).nextInt
generateRandomInt() // Result: -272770531
generateRandomInt() // Result: 217937820
A Pure Function must be Deterministic
and depend only on its inputs
def generateRandomInt(): Int = (new scala.util.Random).nextInt
generateRandomInt() // Result: -272770531
generateRandomInt() // Result: 217937820
A Pure Function must be Deterministic
and depend only on its inputs
def generateRandomInt(): Int = (new scala.util.Random).nextInt
A Pure Function must be Deterministic
and depend only on its inputs
def generateRandomInt(): Int = (new scala.util.Random).nextInt
• Clearly not deterministic!
A Pure Function must be Deterministic
and depend only on its inputs
def generateRandomInt(): Int = (new scala.util.Random).nextInt
• Clearly not deterministic!
• The signature is misleading, because there is a hidden
dependency on a scala.util.Random object
A Pure Function must be Deterministic
and depend only on its inputs
def generateRandomInt(): Int = (new scala.util.Random).nextInt
• Clearly not deterministic!
• The signature is misleading, because there is a hidden
dependency on a scala.util.Random object
• Difficult to test because we can never really be sure how the
function will behave
A Pure Function must be Deterministic
and depend only on its inputs
final case class RNG(seed: Long) {
def nextInt: (Int, RNG) = {
val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
val nextRNG = RNG(newSeed)
val n = (newSeed >>> 16).toInt
(n, nextRNG)
}
}
def generateRandomInt(random: RNG): (Int, RNG) = random.nextInt
val random = RNG(10)
val (n1, random1) = generateRandomInt(random) // n1 = 3847489, random1 = RNG(252149039181)
val (n2, random2) = generateRandomInt(random) // n2 = 3847489, random2 = RNG(252149039181)
val (n3, random3) = generateRandomInt(random2) // n3 = 1334288366, random3 = RNG(87443922374356)
A Pure Function must be Deterministic
and depend only on its inputs
final case class RNG(seed: Long) {
def nextInt: (Int, RNG) = {
val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
val nextRNG = RNG(newSeed)
val n = (newSeed >>> 16).toInt
(n, nextRNG)
}
}
def generateRandomInt(random: RNG): (Int, RNG) = random.nextInt
val random = RNG(10)
val (n1, random1) = generateRandomInt(random) // n1 = 3847489, random1 = RNG(252149039181)
val (n2, random2) = generateRandomInt(random) // n2 = 3847489, random2 = RNG(252149039181)
val (n3, random3) = generateRandomInt(random2) // n3 = 1334288366, random3 = RNG(87443922374356)
A Pure Function must be Deterministic
and depend only on its inputs
final case class RNG(seed: Long) {
def nextInt: (Int, RNG) = {
val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
val nextRNG = RNG(newSeed)
val n = (newSeed >>> 16).toInt
(n, nextRNG)
}
}
def generateRandomInt(random: RNG): (Int, RNG) = random.nextInt
val random = RNG(10)
val (n1, random1) = generateRandomInt(random) // n1 = 3847489, random1 = RNG(252149039181)
val (n2, random2) = generateRandomInt(random) // n2 = 3847489, random2 = RNG(252149039181)
val (n3, random3) = generateRandomInt(random2) // n3 = 1334288366, random3 = RNG(87443922374356)
A Pure Function must be Deterministic
and depend only on its inputs
final case class RNG(seed: Long) {
def nextInt: (Int, RNG) = {
val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
val nextRNG = RNG(newSeed)
val n = (newSeed >>> 16).toInt
(n, nextRNG)
}
}
def generateRandomInt(random: RNG): (Int, RNG) = random.nextInt
val random = RNG(10)
val (n1, random1) = generateRandomInt(random) // n1 = 3847489, random1 = RNG(252149039181)
val (n2, random2) = generateRandomInt(random) // n2 = 3847489, random2 = RNG(252149039181)
val (n3, random3) = generateRandomInt(random2) // n3 = 1334288366, random3 = RNG(87443922374356)
A Pure Function must be Deterministic
and depend only on its inputs
final case class RNG(seed: Long) {
def nextInt: (Int, RNG) = {
val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
val nextRNG = RNG(newSeed)
val n = (newSeed >>> 16).toInt
(n, nextRNG)
}
}
def generateRandomInt(random: RNG): (Int, RNG) = random.nextInt
val random = RNG(10)
val (n1, random1) = generateRandomInt(random) // n1 = 3847489, random1 = RNG(252149039181)
val (n2, random2) = generateRandomInt(random) // n2 = 3847489, random2 = RNG(252149039181)
val (n3, random3) = generateRandomInt(random2) // n3 = 1334288366, random3 = RNG(87443922374356)
A Pure Function must be Deterministic
and depend only on its inputs
final case class RNG(seed: Long) {
def nextInt: (Int, RNG) = {
val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL
val nextRNG = RNG(newSeed)
val n = (newSeed >>> 16).toInt
(n, nextRNG)
}
}
def generateRandomInt(random: RNG): (Int, RNG) = random.nextInt
val random = RNG(10)
val (n1, random1) = generateRandomInt(random) // n1 = 3847489, random1 = RNG(252149039181)
val (n2, random2) = generateRandomInt(random) // n2 = 3847489, random2 = RNG(252149039181)
val (n3, random3) = generateRandomInt(random2) // n3 = 1334288366, random3 = RNG(87443922374356)
A Pure Function must not have side
effects
Finally, a function must not have any side effects:
A Pure Function must not have side
effects
Finally, a function must not have any side effects:
• Memory mutations
A Pure Function must not have side
effects
Finally, a function must not have any side effects:
• Memory mutations
• Interactions with the outside world, such as:
A Pure Function must not have side
effects
Finally, a function must not have any side effects:
• Memory mutations
• Interactions with the outside world, such as:
• Printing messages to the console
A Pure Function must not have side
effects
Finally, a function must not have any side effects:
• Memory mutations
• Interactions with the outside world, such as:
• Printing messages to the console
• Calling an external API
A Pure Function must not have side
effects
Finally, a function must not have any side effects:
• Memory mutations
• Interactions with the outside world, such as:
• Printing messages to the console
• Calling an external API
• Querying a database
A Pure Function must not have side
effects
A pure function can only:
A Pure Function must not have side
effects
A pure function can only:
• Work with immutable values
A Pure Function must not have side
effects
A pure function can only:
• Work with immutable values
• Return an output for a corresponding input
A Pure Function must not have side
effects
var a = 0
def increment(inc: Int): Int = {
a + = inc
a
}
A Pure Function must not have side
effects
def add(a: Int, b: Int): Int = {
println(s "Adding two integers: $a and $b")
a + b
}
FP vs. OOP
FP vs. OOP
• Variables: Immutable / Mutable
FP vs. OOP
• Variables: Immutable / Mutable
• Side effects: NO / YES
FP vs. OOP
• Variables: Immutable / Mutable
• Side effects: NO / YES
• Iterations: Recursion / Loops
FP vs. OOP
FP vs. OOP
• State: Flows through pure
functions / Shared by several
objects
FP vs. OOP
• State: Flows through pure
functions / Shared by several
objects
• Key elements: Immutable values
and Functions / Objects and
Methods
FP vs. OOP
• State: Flows through pure
functions / Shared by several
objects
• Key elements: Immutable values
and Functions / Objects and
Methods
• Suitable for Parallel
programming: YES / Not so
much
Benefits of
Functional
Programming
Benefits of
Functional
Programming
• Local reasoning
Benefits of
Functional
Programming
• Local reasoning
• Referential transparency ->
Fearless refactoring!
Benefits of
Functional
Programming
• Local reasoning
• Referential transparency ->
Fearless refactoring!
• Conciseness -> Fewer bugs!
Benefits of
Functional
Programming
Benefits of
Functional
Programming
• Easier to test
Benefits of
Functional
Programming
• Easier to test
• Applications behave more
predictably
Benefits of
Functional
Programming
• Easier to test
• Applications behave more
predictably
• Allows us to write correct parallel
programs
Functional Effects
Functional Effects
• Descriptions of interactions with
the outside world
Functional Effects
• Descriptions of interactions with
the outside world
• Immutable values that can serve
as inputs and outputs of pure
functions
Functional Effects
• Descriptions of interactions with
the outside world
• Immutable values that can serve
as inputs and outputs of pure
functions
• They are executed only at the
End of the World
Enter ZIO!
ZIO - The Library
Allows us to build modern applications, using the
principles of Functional Programming!
ZIO - The Library
ZIO - The Library
• Asynchronous & Concurrent -> Fiber-based model!
ZIO - The Library
• Asynchronous & Concurrent -> Fiber-based model!
• Resilient -> Leverages the power of Scala's Type System!
ZIO - The Library
• Asynchronous & Concurrent -> Fiber-based model!
• Resilient -> Leverages the power of Scala's Type System!
• Efficient -> Apps that never leak resources!
ZIO - The Library
• Asynchronous & Concurrent -> Fiber-based model!
• Resilient -> Leverages the power of Scala's Type System!
• Efficient -> Apps that never leak resources!
• Easy to understand and test -> Thanks to superior
composability!
ZIO - The Data Type
ZIO[-R, +E, +A]
ZIO - The Data Type
ZIO[-R, +E, +A]
• Core type of the ZIO Library
ZIO - The Data Type
ZIO[-R, +E, +A]
• Core type of the ZIO Library
• Functional Effect
ZIO - The Data Type
A good mental model is the following:
R => Either[E, A]
This means that a ZIO effect:
ZIO - The Data Type
A good mental model is the following:
R => Either[E, A]
This means that a ZIO effect:
• Needs an environment of type R to run
ZIO - The Data Type
A good mental model is the following:
R => Either[E, A]
This means that a ZIO effect:
• Needs an environment of type R to run
• It may fail with an error of type E
ZIO - The Data Type
A good mental model is the following:
R => Either[E, A]
This means that a ZIO effect:
• Needs an environment of type R to run
• It may fail with an error of type E
• Or it may complete successfully, returning a value of type A
ZIO - The Data Type
Common aliases:
Task[+A] = ZIO[Any, Throwable, +A]
UIO[+A] = ZIO[Any, Nothing, +A]
RIO[-R, +A] = ZIO[-R, Throwable, +A]
IO[+E, +A] = ZIO[Any, E, A]
URIO[-R, +A] = ZIO[R, Nothing, A]
Live coding
Tic-Tac-Toe game with ZIO!
https://github.com/jorge-vasquez-2301/zio-tictactoe
Where to learn more
• Introduction to Programming with ZIO Functional Effects - Scalac Blog
• Introducción a la Programación con Efectos Funcionales usando ZIO -
Scalac Blog
• Mastering modularity in ZIO with ZLayer - Scalac Blog
• How to write a (completely lock-free) concurrent LRU Cache with ZIO STM
• ZIO Official Site
• Zionomicon
Where to learn more
Learn how to use the full potential of Functional
Programming with Scalac Trainings!
• Scala 2 for Java Developers
• Scala 3 for Scala 2 Developers
• ZIO
We are hiring!
https://scalac.io/careers/
@majakryzan
maja.kryzan@scalac.io
Contact me
@jorvasquez2301
jorge-vasquez-2301
jorge.vasquez@scalac.io

Functional Programming 101 with Scala and ZIO @FunctionalWorld

  • 1.
    Functional Programming 101 with Scalaand ZIO Functional World April 15th, 2021
  • 2.
  • 3.
  • 4.
  • 5.
    Agenda • Functional Programming(FP) basic concepts • ZIO basic concepts
  • 6.
    Agenda • Functional Programming(FP) basic concepts • ZIO basic concepts • Live coding: Tic-Tac-Toe game
  • 7.
  • 8.
    What is FunctionalProgramming? Programming paradigm, where programs are a composition of pure functions
  • 9.
  • 10.
    Characteristics of a PureFunction • Total
  • 11.
    Characteristics of a PureFunction • Total • Deterministic and depends on its inputs only
  • 12.
    Characteristics of a PureFunction • Total • Deterministic and depends on its inputs only • Must not have side effects
  • 13.
    A Pure Functionmust be Total For each input that is provided to the function there must be a defined output
  • 14.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Int = a / b divide(5, 0) // java.lang.ArithmeticException: / by zero
  • 15.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Int = a / b divide(5, 0) // java.lang.ArithmeticException: / by zero
  • 16.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Int = a / b divide(5, 0) // java.lang.ArithmeticException: / by zero
  • 17.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Int = a / b divide(5, 0) // java.lang.ArithmeticException: / by zero
  • 18.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Int = a / b
  • 19.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Int = a / b • The signature of this function tells a lie!
  • 20.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Int = a / b • The signature of this function tells a lie! • Every time we call it we will have to be very careful
  • 21.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Int = a / b • The signature of this function tells a lie! • Every time we call it we will have to be very careful • Runtime exceptions can happen. The compiler is not able to do anything to help us to avoid this
  • 22.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Option[Int] = if (b != 0) Some(a / b) else None divide(5, 0) // None
  • 23.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Option[Int] = if (b != 0) Some(a / b) else None divide(5, 0) // None
  • 24.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Option[Int] = if (b != 0) Some(a / b) else None divide(5, 0) // None
  • 25.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Option[Int] = if (b != 0) Some(a / b) else None divide(5, 0) // None
  • 26.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Option[Int] = if (b != 0) Some(a / b) else None
  • 27.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Option[Int] = if (b != 0) Some(a / b) else None • The function’s signature clearly communicates that some inputs are not handled
  • 28.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Option[Int] = if (b != 0) Some(a / b) else None • The function’s signature clearly communicates that some inputs are not handled • The compiler will force us to consider the case in which the result is not defined
  • 29.
    A Pure Functionmust be Total def divide(a: Int, b: Int): Option[Int] = if (b != 0) Some(a / b) else None • The function’s signature clearly communicates that some inputs are not handled • The compiler will force us to consider the case in which the result is not defined • No runtime exceptions!
  • 30.
    A Pure Functionmust be Deterministic and depend only on its inputs For each input that is provided to the function, the same output must be returned, no matter how many times the function is called
  • 31.
    A Pure Functionmust be Deterministic and depend only on its inputs def generateRandomInt(): Int = (new scala.util.Random).nextInt generateRandomInt() // Result: -272770531 generateRandomInt() // Result: 217937820
  • 32.
    A Pure Functionmust be Deterministic and depend only on its inputs def generateRandomInt(): Int = (new scala.util.Random).nextInt generateRandomInt() // Result: -272770531 generateRandomInt() // Result: 217937820
  • 33.
    A Pure Functionmust be Deterministic and depend only on its inputs def generateRandomInt(): Int = (new scala.util.Random).nextInt generateRandomInt() // Result: -272770531 generateRandomInt() // Result: 217937820
  • 34.
    A Pure Functionmust be Deterministic and depend only on its inputs def generateRandomInt(): Int = (new scala.util.Random).nextInt generateRandomInt() // Result: -272770531 generateRandomInt() // Result: 217937820
  • 35.
    A Pure Functionmust be Deterministic and depend only on its inputs def generateRandomInt(): Int = (new scala.util.Random).nextInt
  • 36.
    A Pure Functionmust be Deterministic and depend only on its inputs def generateRandomInt(): Int = (new scala.util.Random).nextInt • Clearly not deterministic!
  • 37.
    A Pure Functionmust be Deterministic and depend only on its inputs def generateRandomInt(): Int = (new scala.util.Random).nextInt • Clearly not deterministic! • The signature is misleading, because there is a hidden dependency on a scala.util.Random object
  • 38.
    A Pure Functionmust be Deterministic and depend only on its inputs def generateRandomInt(): Int = (new scala.util.Random).nextInt • Clearly not deterministic! • The signature is misleading, because there is a hidden dependency on a scala.util.Random object • Difficult to test because we can never really be sure how the function will behave
  • 39.
    A Pure Functionmust be Deterministic and depend only on its inputs final case class RNG(seed: Long) { def nextInt: (Int, RNG) = { val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL val nextRNG = RNG(newSeed) val n = (newSeed >>> 16).toInt (n, nextRNG) } } def generateRandomInt(random: RNG): (Int, RNG) = random.nextInt val random = RNG(10) val (n1, random1) = generateRandomInt(random) // n1 = 3847489, random1 = RNG(252149039181) val (n2, random2) = generateRandomInt(random) // n2 = 3847489, random2 = RNG(252149039181) val (n3, random3) = generateRandomInt(random2) // n3 = 1334288366, random3 = RNG(87443922374356)
  • 40.
    A Pure Functionmust be Deterministic and depend only on its inputs final case class RNG(seed: Long) { def nextInt: (Int, RNG) = { val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL val nextRNG = RNG(newSeed) val n = (newSeed >>> 16).toInt (n, nextRNG) } } def generateRandomInt(random: RNG): (Int, RNG) = random.nextInt val random = RNG(10) val (n1, random1) = generateRandomInt(random) // n1 = 3847489, random1 = RNG(252149039181) val (n2, random2) = generateRandomInt(random) // n2 = 3847489, random2 = RNG(252149039181) val (n3, random3) = generateRandomInt(random2) // n3 = 1334288366, random3 = RNG(87443922374356)
  • 41.
    A Pure Functionmust be Deterministic and depend only on its inputs final case class RNG(seed: Long) { def nextInt: (Int, RNG) = { val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL val nextRNG = RNG(newSeed) val n = (newSeed >>> 16).toInt (n, nextRNG) } } def generateRandomInt(random: RNG): (Int, RNG) = random.nextInt val random = RNG(10) val (n1, random1) = generateRandomInt(random) // n1 = 3847489, random1 = RNG(252149039181) val (n2, random2) = generateRandomInt(random) // n2 = 3847489, random2 = RNG(252149039181) val (n3, random3) = generateRandomInt(random2) // n3 = 1334288366, random3 = RNG(87443922374356)
  • 42.
    A Pure Functionmust be Deterministic and depend only on its inputs final case class RNG(seed: Long) { def nextInt: (Int, RNG) = { val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL val nextRNG = RNG(newSeed) val n = (newSeed >>> 16).toInt (n, nextRNG) } } def generateRandomInt(random: RNG): (Int, RNG) = random.nextInt val random = RNG(10) val (n1, random1) = generateRandomInt(random) // n1 = 3847489, random1 = RNG(252149039181) val (n2, random2) = generateRandomInt(random) // n2 = 3847489, random2 = RNG(252149039181) val (n3, random3) = generateRandomInt(random2) // n3 = 1334288366, random3 = RNG(87443922374356)
  • 43.
    A Pure Functionmust be Deterministic and depend only on its inputs final case class RNG(seed: Long) { def nextInt: (Int, RNG) = { val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL val nextRNG = RNG(newSeed) val n = (newSeed >>> 16).toInt (n, nextRNG) } } def generateRandomInt(random: RNG): (Int, RNG) = random.nextInt val random = RNG(10) val (n1, random1) = generateRandomInt(random) // n1 = 3847489, random1 = RNG(252149039181) val (n2, random2) = generateRandomInt(random) // n2 = 3847489, random2 = RNG(252149039181) val (n3, random3) = generateRandomInt(random2) // n3 = 1334288366, random3 = RNG(87443922374356)
  • 44.
    A Pure Functionmust be Deterministic and depend only on its inputs final case class RNG(seed: Long) { def nextInt: (Int, RNG) = { val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL val nextRNG = RNG(newSeed) val n = (newSeed >>> 16).toInt (n, nextRNG) } } def generateRandomInt(random: RNG): (Int, RNG) = random.nextInt val random = RNG(10) val (n1, random1) = generateRandomInt(random) // n1 = 3847489, random1 = RNG(252149039181) val (n2, random2) = generateRandomInt(random) // n2 = 3847489, random2 = RNG(252149039181) val (n3, random3) = generateRandomInt(random2) // n3 = 1334288366, random3 = RNG(87443922374356)
  • 45.
    A Pure Functionmust not have side effects Finally, a function must not have any side effects:
  • 46.
    A Pure Functionmust not have side effects Finally, a function must not have any side effects: • Memory mutations
  • 47.
    A Pure Functionmust not have side effects Finally, a function must not have any side effects: • Memory mutations • Interactions with the outside world, such as:
  • 48.
    A Pure Functionmust not have side effects Finally, a function must not have any side effects: • Memory mutations • Interactions with the outside world, such as: • Printing messages to the console
  • 49.
    A Pure Functionmust not have side effects Finally, a function must not have any side effects: • Memory mutations • Interactions with the outside world, such as: • Printing messages to the console • Calling an external API
  • 50.
    A Pure Functionmust not have side effects Finally, a function must not have any side effects: • Memory mutations • Interactions with the outside world, such as: • Printing messages to the console • Calling an external API • Querying a database
  • 51.
    A Pure Functionmust not have side effects A pure function can only:
  • 52.
    A Pure Functionmust not have side effects A pure function can only: • Work with immutable values
  • 53.
    A Pure Functionmust not have side effects A pure function can only: • Work with immutable values • Return an output for a corresponding input
  • 54.
    A Pure Functionmust not have side effects var a = 0 def increment(inc: Int): Int = { a + = inc a }
  • 55.
    A Pure Functionmust not have side effects def add(a: Int, b: Int): Int = { println(s "Adding two integers: $a and $b") a + b }
  • 56.
  • 57.
    FP vs. OOP •Variables: Immutable / Mutable
  • 58.
    FP vs. OOP •Variables: Immutable / Mutable • Side effects: NO / YES
  • 59.
    FP vs. OOP •Variables: Immutable / Mutable • Side effects: NO / YES • Iterations: Recursion / Loops
  • 60.
  • 61.
    FP vs. OOP •State: Flows through pure functions / Shared by several objects
  • 62.
    FP vs. OOP •State: Flows through pure functions / Shared by several objects • Key elements: Immutable values and Functions / Objects and Methods
  • 63.
    FP vs. OOP •State: Flows through pure functions / Shared by several objects • Key elements: Immutable values and Functions / Objects and Methods • Suitable for Parallel programming: YES / Not so much
  • 64.
  • 65.
  • 66.
    Benefits of Functional Programming • Localreasoning • Referential transparency -> Fearless refactoring!
  • 67.
    Benefits of Functional Programming • Localreasoning • Referential transparency -> Fearless refactoring! • Conciseness -> Fewer bugs!
  • 68.
  • 69.
  • 70.
    Benefits of Functional Programming • Easierto test • Applications behave more predictably
  • 71.
    Benefits of Functional Programming • Easierto test • Applications behave more predictably • Allows us to write correct parallel programs
  • 74.
  • 75.
    Functional Effects • Descriptionsof interactions with the outside world
  • 76.
    Functional Effects • Descriptionsof interactions with the outside world • Immutable values that can serve as inputs and outputs of pure functions
  • 77.
    Functional Effects • Descriptionsof interactions with the outside world • Immutable values that can serve as inputs and outputs of pure functions • They are executed only at the End of the World
  • 79.
  • 80.
    ZIO - TheLibrary Allows us to build modern applications, using the principles of Functional Programming!
  • 81.
    ZIO - TheLibrary
  • 82.
    ZIO - TheLibrary • Asynchronous & Concurrent -> Fiber-based model!
  • 83.
    ZIO - TheLibrary • Asynchronous & Concurrent -> Fiber-based model! • Resilient -> Leverages the power of Scala's Type System!
  • 84.
    ZIO - TheLibrary • Asynchronous & Concurrent -> Fiber-based model! • Resilient -> Leverages the power of Scala's Type System! • Efficient -> Apps that never leak resources!
  • 85.
    ZIO - TheLibrary • Asynchronous & Concurrent -> Fiber-based model! • Resilient -> Leverages the power of Scala's Type System! • Efficient -> Apps that never leak resources! • Easy to understand and test -> Thanks to superior composability!
  • 86.
    ZIO - TheData Type ZIO[-R, +E, +A]
  • 87.
    ZIO - TheData Type ZIO[-R, +E, +A] • Core type of the ZIO Library
  • 88.
    ZIO - TheData Type ZIO[-R, +E, +A] • Core type of the ZIO Library • Functional Effect
  • 89.
    ZIO - TheData Type A good mental model is the following: R => Either[E, A] This means that a ZIO effect:
  • 90.
    ZIO - TheData Type A good mental model is the following: R => Either[E, A] This means that a ZIO effect: • Needs an environment of type R to run
  • 91.
    ZIO - TheData Type A good mental model is the following: R => Either[E, A] This means that a ZIO effect: • Needs an environment of type R to run • It may fail with an error of type E
  • 92.
    ZIO - TheData Type A good mental model is the following: R => Either[E, A] This means that a ZIO effect: • Needs an environment of type R to run • It may fail with an error of type E • Or it may complete successfully, returning a value of type A
  • 93.
    ZIO - TheData Type Common aliases: Task[+A] = ZIO[Any, Throwable, +A] UIO[+A] = ZIO[Any, Nothing, +A] RIO[-R, +A] = ZIO[-R, Throwable, +A] IO[+E, +A] = ZIO[Any, E, A] URIO[-R, +A] = ZIO[R, Nothing, A]
  • 94.
    Live coding Tic-Tac-Toe gamewith ZIO! https://github.com/jorge-vasquez-2301/zio-tictactoe
  • 95.
    Where to learnmore • Introduction to Programming with ZIO Functional Effects - Scalac Blog • Introducción a la Programación con Efectos Funcionales usando ZIO - Scalac Blog • Mastering modularity in ZIO with ZLayer - Scalac Blog • How to write a (completely lock-free) concurrent LRU Cache with ZIO STM • ZIO Official Site • Zionomicon
  • 96.
    Where to learnmore Learn how to use the full potential of Functional Programming with Scalac Trainings! • Scala 2 for Java Developers • Scala 3 for Scala 2 Developers • ZIO
  • 97.
  • 98.