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.