$30 off During Our Annual Pro Sale. View Details »

ZIO from Home

ZIO from Home

ZIO provides a variety of features for building synchronous, asynchronous and concurrent applications.

In this talk, I will show how to use functional effects and data types built on ZIO how to manage errors and recover from them, how to manage resources and how to make concurrent tasks.

Signify

May 28, 2020
Tweet

More Decks by Signify

Other Decks in Technology

Transcript

  1. ZIO from
    Home
    Wiem Zine Elabidine
    @WiemZin
    Stay Safe

    View Slide

  2. Functional Effects

    View Slide

  3. Functional Programming
    Pure functions
    Referential
    transparency.
    Composability
    Combine functions
    together to build
    new data.
    Immutability
    Values couldn’t be
    changed.
    Data & Functionality
    Pass data through
    functions to
    change its
    behaviors.

    View Slide

  4. Stay home safe and
    use what you have
    No Side effects
    Pure Function
    Trust your types
    Total
    Test your functions
    Deterministic

    View Slide

  5. Stay home safe and
    use what you have
    No Side effects
    Pure Function
    Trust your types
    Total
    Test your functions
    Deterministic
    def toInt(str: String): Try[Int] =
    Try(str.toInt)

    View Slide

  6. Stay home safe and
    use what you have
    No Side effects
    Pure Function
    Trust your types
    Total
    Test your functions
    Deterministic
    def toInt(str: String): Try[Int] =
    Try(str.toInt)
    toInt("One")

    View Slide

  7. Stay home safe and
    use what you have
    No Side effects
    Pure Function
    Trust your types
    Total
    Test your functions
    Deterministic
    def toInt(str: String): Try[Int] =
    Try(str.toInt)
    toInt("One")
    Failure(java.lang.NumberFormatException: For input string: "One")

    View Slide

  8. Stay home safe and
    use what you have
    No Side effects
    Pure Function
    Trust your types
    Total
    Test your functions
    Deterministic
    def nextDay(day: DayOfWeek): String =
    day.plus(1) .toString
    assert(nextDay(THURSDAY)))(equalTo("FRIDAY"))

    View Slide

  9. Stay home safe and
    use what you have
    No Side effects
    Pure Function
    Trust your types
    Total
    Test your functions
    Deterministic
    def nextDay(day: DayOfWeek): String =
    day.plus(1) .toString
    assert(nextDay(THURSDAY)))(equalTo("FRIDAY"))

    View Slide

  10. Stay home safe and
    use what you have
    No Side effects
    Pure Function
    Trust your types
    Total
    Test your functions
    Deterministic
    def add(pasta: Pasta, sauce: Sauce,
    water: Water): Cooked[Pasta] = {
    val p = water.boil.map(_.put(pasta))
    p.addSauce(sauce)
    }

    View Slide

  11. Stay home safe and
    use what you have
    No Side effects
    Pure Function
    Trust your types
    Total
    Test your functions
    Deterministic

    View Slide

  12. Effects are useful
    ❏ handle events
    ❏ Send messages
    ❏ read from the DataBase
    ❏ persist information
    ❏ print out the result
    ❏ retry in event of errors
    ❏ send result to other
    services
    ❏ ...
    Real World Applications

    View Slide

  13. Abstract your programs!
    Describe everything in
    immutable data type!
    Functional Effects
    Effects as values?

    View Slide

  14. Functional Effects
    case class IO[A](unsafeRun: () => A) {
    def map[B](f: A => B): IO[B] = ???
    def flatMap[B](f: A => IO[B]): IO[B] = ???
    }
    object IO {
    def effect[A](a: => A): IO[A] = new IO[A](() => a)
    }

    View Slide

  15. Functional Program
    val program: IO[Unit] = for {
    event <- IO.effect(handleEvent)
    user <- IO.effect(getUser(event.userId))
    _ <- IO.effect(logInfo(user))
    ...
    } yield ()

    View Slide

  16. Functional Program
    val program: IO[Unit] = for {
    event <- IO.effect(handleEvent)
    user <- IO.effect(getUser(event.userId))
    _ <- IO.effect(logInfo(user))
    ...
    } yield ()
    program.unsafeRun()

    View Slide

  17. Execution
    Just do it!
    Description
    Make a plan A - Z
    Be prepared!

    View Slide

  18. ● Control over all type of interactions
    ● Testability
    ● Refactoring
    Why Functional Effects?

    View Slide

  19. Functional Effects
    case class IO[A](unsafeRun: () => A) {
    def map[B](f: A => B): IO[B] = ???
    def flatMap[B](f: A => IO[B]): IO[B] = ???
    }
    object IO {
    def effect[A](a: => A): IO[A] = new IO[A](() => a)
    }

    View Slide

  20. ZIO
    Zero dependency Scala library for asynchronous and concurrent
    programming using purely functional code.

    View Slide

  21. Functional Effects in ZIO
    ZIO[R, E, A]
    Description of a program
    R
    E A
    Dependencies
    Error Success

    View Slide

  22. Functional Effects in ZIO
    RIO[R, A]
    Description of a program
    R
    Throwable
    A
    Dependencies
    Error Success

    View Slide

  23. Functional Effects in ZIO
    URIO[R, A]
    Description of a program
    R
    A
    Dependencies
    Success

    View Slide

  24. Functional Effects in ZIO
    Task[A]
    Description of a program
    Throwable
    A
    Error Success

    View Slide

  25. Functional Effects in ZIO
    IO[E, A]
    Description of a program
    E
    A
    Error Success

    View Slide

  26. Functional Effects in ZIO
    UIO[A]
    Description of a program
    A
    Success

    View Slide

  27. Run Effects
    object Main extends zio.App {
    override def run(args: List[String]): IO[Nothing, Int] =
    program.fold(_ => 1, _ => 0)
    }
    OR
    object Main {
    Runtime.default.unsafeRun(program)
    }

    View Slide

  28. Error
    Management

    View Slide

  29. Error Management
    Throwable
    def divideM(a: Int, b: Int): Task[Int] =
    Task(divide(a, b))
    Task[Int]
    Throwable Int

    View Slide

  30. Error Management
    Throwable
    val throwException: Task[Nothing] =
    Task(throw new Exception("sorry"))
    Task[Nothing]
    Throwable

    View Slide

  31. Error Management
    String
    val failedIO: IO[String, Nothing] = IO.fail("sorry again")
    IO[String, Nothing]
    String

    View Slide

  32. Error Management
    Customized Errors
    sealed trait Error
    object Error {
    case class UserNotFound(id: UserId) extends Error
    case class InternalServer(t: Throwable) extends Error
    ...
    }
    val program: IO[Error, Unit] = ???

    View Slide

  33. Error Management
    Customized Errors
    val program: IO[Error, Unit] =
    for {
    userId <- requestListener // IO[InternalServer, UserId]
    user <- getUser(userId) // IO[UserNotFound, User]
    _ <- logInfo(user) // IO[ Nothing, Unit]
    _ <- sendResponse(user) // IO[InternalServer, Unit]
    } yield ()

    View Slide

  34. Error Management
    The state of the program
    val program: IO[Error, Unit] = ???
    val programState: IO[Nothing, Exit[Error, Unit]] = program.run

    View Slide

  35. Error Management
    Exit[E, A]
    Success[A]
    Failure[E]

    View Slide

  36. Error Management
    Exit[E, A]
    Success[A]
    Failure[E]

    View Slide

  37. Error Management
    Exit[E, A]
    Success[A]
    Failure[E]
    Cause[E]

    View Slide

  38. Cause[E]
    Die
    Expected Error
    Unexpected Error, Exception
    Fail[E]
    Interrupted effect
    Interrupt

    View Slide

  39. Many causes?
    Both(left, right)
    Both(Fail(e1),
    Then(Both(Fail(e2), Die(t)), Interrupt)
    Then(left, right)

    View Slide

  40. Example: Cause.Both
    IO.fail("error1")
    .zipPar(
    IO.succeed(throw new Exception(" surprise!"))
    )

    View Slide

  41. Example: Cause.Both
    IO.fail("error1") // Cause.Fail("error1")
    .zipPar(
    IO.succeed(throw new Exception(" surprise!"))
    )

    View Slide

  42. Example: Cause.Both
    IO.fail("error1") // Cause.Fail("error1")
    .zipPar(
    IO.succeed(throw new Exception(" surprise!")) // Cause.Die(...)
    )

    View Slide

  43. Example: Cause.Both
    IO.fail("error1") // Cause.Fail("error1")
    .zipPar(
    IO.succeed(throw new Exception(" surprise!")) // Cause.Die(...)
    )
    ⇒ Both(Cause.Fail("error1"), Cause.Die(java.lang.Exception: surprise!)))

    View Slide

  44. Example: Cause.Then
    IO.fail("error").ensuring(IO.die(new Exception("Don't try this at Home")))

    View Slide

  45. Example: Cause.Then
    IO.fail("error").ensuring(IO.die(new Exception("Don't try this at Home")))
    Fail("error")

    View Slide

  46. Example: Cause.Then
    IO.fail("error").ensuring(IO.die(new Exception("Don't try this at Home")))
    Fail("error") Die(java.lang.Exception Don’t try this at Home)

    View Slide

  47. Example: Cause.Then
    IO.fail("error").ensuring(IO.die(new Exception("Don't try this at Home")))
    Fail("error") Die(java.lang.Exception Don’t try this at Home)
    ⇒ Then(Cause.Fail("error"), Cause.Die(java.lang.Exception: Don’t try this at Home))

    View Slide

  48. Error Management
    Expose all causes:
    def divide(a: Int, b: Int): IO[Cause[Throwable], Int] =
    Task(a / b).sandbox

    View Slide

  49. Error Management
    Catch Errors:
    def divide(a: Int, b: Int): Task[Int] =
    Task(a / b)
    .catchSome{
    case _: ArithmeticException => UIO(0)
    }

    View Slide

  50. Error Management
    Peek at the errors:
    def divide(a: Int, b: Int): Task[Int] =
    Task(a / b)
    .tapError{
    error => UIO(println(s"failed with: $e"))
    }

    View Slide

  51. Error Management
    Fallback:
    val io1: IO[Error, Int] = ???
    val io2: IO[String, Int] = ???
    val result: IO[String, Int] = io1.orElse(io2)

    View Slide

  52. Error Management
    Fallback:
    val loginUser: IO[Error, Profile] = ???
    val loginAnonymous: IO[Throwable, LimitedProfile] = ???
    val result: IO[Throwable, Either[Profile, LimitedProfile]] =
    loginUser.orElseEither(loginAnonymous)

    View Slide

  53. Error Management
    Recover:
    def divide(a: Int, b: Int): UIO[Int] =
    Task(a / b).foldM(_ => UIO(0), n => UIO(n))

    View Slide

  54. Error Management
    Crash it:
    def divide(a: Int, b: Int): UIO[Int] =
    Task(a / b).orDie

    View Slide

  55. Error Management
    Make defects as expected errors:
    val io: IO[String, Nothing] =
    IO.succeed(throw new Exception(""))
    .unrefine(e => s"The error is: $e")

    View Slide

  56. Error Management
    What about fatal Errors?

    View Slide

  57. Error Management
    def simpleName[A](c: Class[A]) = c.getSimpleName
    object Example
    Task(simpleName(Example.getClass))

    View Slide

  58. Error Management
    def simpleName[A](c: Class[A]) = c.getSimpleName
    object Example
    Task(simpleName(Example.getClass))
    [error] java.lang.InternalError: Malformed class name
    [error] at java.lang.Class.getSimpleName(Class.java:1330)

    View Slide

  59. Error Management
    object Main extends zio.App {
    override val platform: Platform =
    Platform.default.withFatal (_ => false)
    override def run(args: List[String]): ZIO[zio.ZEnv, Nothing, Int] = {
    Task(simpleName(Example.getClass)).fold(_ =>1, _ => 0)
    }
    }

    View Slide

  60. Error Management
    override val platform: Platform = new Platform {
    val executor = Executor.makeDefault(2)
    val tracing = Tracing.disabled
    def fatal(t: Throwable): Boolean =
    !t.isInstanceOf[InternalError] && t.isInstanceOf[VirtualMachineError]
    def reportFatal(t: Throwable): Nothing = {
    t.printStackTrace()
    throw t
    }
    def reportFailure(cause: Cause[Any]): Unit = {
    if (cause.died)
    System.err.println(cause.prettyPrint)
    }
    ...
    }

    View Slide

  61. Build concurrent
    programs

    View Slide

  62. ZIO Fibers
    Fibers are lightweight mechanism of concurrency.
    OS Thread
    ZIO Fiber
    Task1 Task2 Task3 ... ..

    View Slide

  63. How ZIO runs Effects?
    ZIO[R, E, A]

    View Slide

  64. How ZIO runs Effects?
    ZIO[R, E, A]
    R => IO[E, A]

    View Slide

  65. How ZIO runs Effects?
    ZIO[R, E, A]
    R => IO[E, A]

    View Slide

  66. How ZIO runs Effects?
    ZIO[R, E, A]
    R => IO[E, A]

    View Slide

  67. How ZIO runs Effects?
    IO[E, A]

    View Slide

  68. How ZIO runs Effects?
    IO[E, A]
    Fiber[E, A]
    unsafeRun

    View Slide

  69. How ZIO runs Effects?
    IO[E, A]
    Fiber[E, A]
    unsafeRun

    View Slide

  70. How ZIO runs Effects?
    IO[E, A]
    Fiber[E, A]
    unsafeRun

    View Slide

  71. How ZIO runs concurrent Effects?
    IO[E, A]
    Fiber[E, A]
    unsafeRun
    IO[Nothing,Fiber[E, A]]
    fork

    View Slide

  72. How ZIO runs concurrent Effects?
    IO[E, A]
    Fiber[E, A]
    unsafeRun
    IO[Nothing,Fiber[E, A]]
    fork

    View Slide

  73. How ZIO runs concurrent Effects?
    IO[E, A]
    Fiber[E, A]
    unsafeRun
    IO[Nothing,Fiber[E, A]]
    fork
    Fiber[E, A]
    unsafeRun

    View Slide

  74. How ZIO runs concurrent Effects?
    IO[E, A]
    Fiber[E, A]
    unsafeRun
    IO[Nothing,Fiber[E, A]]
    fork
    Fiber[E, A]
    unsafeRun

    View Slide

  75. Concurrent Tasks
    trait ZIO[R, E, A] {
    def race(that: ZIO[R, E, A]): ZIO[R, E, A]
    def raceAll(ios: Iterable[ZIO[R, E, A]]): ZIO[R, E, A]
    def zipPar(that: ZIO[R, E, B]): ZIO[R, E, (A, B)]
    def on(ec: ExecutionContext): ZIO[R, E, A]
    ...
    }
    object ZIO {
    def foreachPar(as: Iterable[A])(fn: A => ZIO[R, E, B]): ZIO[R, E, List[B]]
    ...
    }

    View Slide

  76. Concurrent Tasks
    def getUserInfo(id: Id): Task[(User, Profile)] =
    fetchUser(id).zipPar(fetchProfile(id))
    Task 1 Task 2
    (User, Profile)

    View Slide

  77. Example
    case class IlForno(queue: Queue[Request], currentIngredients: Ref[Ingredients]) {
    def handleRequests(p: Promise[Nothing, Unit]): ZIO[Clock, MissingIngredient, Unit] =
    (for {
    request <- queue.take
    rest <- currentIngredients.update(preparePizza(request, _))
    _ <- evaluate(rest)
    } yield ())
    .tapError(_ => p.succeed(()))
    .repeat(Schedule.duration(8.hours) && Schedule.spaced(10.minutes))
    .unit
    val listenRequests: IO[Error, Unit] = ???
    }

    View Slide

  78. val program: ZIO[Clock, Error, Unit] = for {
    ilForno <- IlForno(initialIngredient)
    f1 <- ilForno.listenRequests.fork
    p <- Promise.make[Nothing, Unit]
    f2 <- ilForno.handleRequests(p).fork
    _ <- p.await
    _ <- f1.interrupt.zipPar(f2.interrupt)
    } yield ()
    Example

    View Slide

  79. Example
    val program: ZIO[Clock, Error, Unit] = for {
    ilForno <- IlForno(initialIngredient)
    f1 <- ilForno.listenRequests.fork
    p <- Promise.make[Nothing, Unit]
    f2 <- ilForno.handleRequests(p).fork
    _ <- p.await
    _ <- f1.interrupt.zipPar(f2.interrupt)
    } yield ()

    View Slide

  80. Example
    val program: ZIO[Clock, Error, Unit] = for {
    ilForno <- IlForno(initialIngredient)
    f1 <- ilForno.listenRequests.fork
    p <- Promise.make[Nothing, Unit]
    f2 <- ilForno.handleRequests(p).fork
    _ <- p.await
    _ <- f1.interrupt.zipPar(f2.interrupt)
    } yield ()

    View Slide

  81. Example
    val program: ZIO[Clock, Error, Unit] = for {
    ilForno <- IlForno(initialIngredient)
    f1 <- ilForno.listenRequests.fork
    p <- Promise.make[Nothing, Unit]
    f2 <- ilForno.handleRequests(p).fork
    _ <- p.await
    _ <- f1.interrupt.zipPar(f2.interrupt)
    } yield ()

    View Slide

  82. Resource
    Management

    View Slide

  83. Resource Management
    File Connection
    Open / close / use = Acquire / release / use
    Database Clients

    View Slide

  84. Resource Management
    IO.effect(startApp)
    .ensuring(
    console.putStr("Shutdown ..."))
    ensuring
    01

    View Slide

  85. Resource Management
    IO.effect(startApp)
    .ensuring(
    console.putStr("Shutdown ..."))
    createClient(config)
    .bracket(_.close)(c =>
    processEvents(c))
    ensuring bracket
    01 02

    View Slide

  86. Resource Management
    IO.effect(startApp)
    .ensuring(
    console.putStr("Shutdown ..."))
    ensuring
    val resource =
    Managed.make(openFile(file))(_.close)
    ...
    resource.use(computeLines)
    Management
    bracket
    01 03
    02
    createClient(config)
    .bracket(_.close)(c =>
    processEvents(c))

    View Slide

  87. Resource Management
    ● You can use the methods provided by the dependencies in your
    program.
    ● once you provide a layer or an environment, the implementation
    will be acquired and used then released at the end.
    ZIO Environment & ZLayer[R, E, A]

    View Slide

  88. ZIO
    is Awesome

    View Slide

  89. WrapUp
    documentation
    https://github.com/zio/zio
    zio.dev
    ZIO projects
    Blog post
    https://medium.com/@wiemzin

    View Slide

  90. CREDITS: This presentation template was
    created by Slidesgo, including icons by Flaticon,
    and infographics & images by Freepik
    THANKS
    Follow me: @WiemZin
    Please keep this slide for attribution

    View Slide