Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. 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.
  2. Stay home safe and use what you have No Side

    effects Pure Function Trust your types Total Test your functions Deterministic
  3. 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)
  4. 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")
  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) toInt("One") Failure(java.lang.NumberFormatException: For input string: "One")
  6. 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"))
  7. 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"))
  8. 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) }
  9. Stay home safe and use what you have No Side

    effects Pure Function Trust your types Total Test your functions Deterministic
  10. 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
  11. 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) }
  12. Functional Program val program: IO[Unit] = for { event <-

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

    IO.effect(handleEvent) user <- IO.effect(getUser(event.userId)) _ <- IO.effect(logInfo(user)) ... } yield () program.unsafeRun()
  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) }
  15. Functional Effects in ZIO ZIO[R, E, A] Description of a

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

    R Throwable A Dependencies Error Success
  17. 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) }
  18. Error Management Throwable def divideM(a: Int, b: Int): Task[Int] =

    Task(divide(a, b)) Task[Int] Throwable Int
  19. 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] = ???
  20. 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 ()
  21. Error Management The state of the program val program: IO[Error,

    Unit] = ??? val programState: IO[Nothing, Exit[Error, Unit]] = program.run
  22. 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!)))
  23. 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))
  24. Error Management Expose all causes: def divide(a: Int, b: Int):

    IO[Cause[Throwable], Int] = Task(a / b).sandbox
  25. Error Management Catch Errors: def divide(a: Int, b: Int): Task[Int]

    = Task(a / b) .catchSome{ case _: ArithmeticException => UIO(0) }
  26. 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")) }
  27. Error Management Fallback: val io1: IO[Error, Int] = ??? val

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

    loginAnonymous: IO[Throwable, LimitedProfile] = ??? val result: IO[Throwable, Either[Profile, LimitedProfile]] = loginUser.orElseEither(loginAnonymous)
  29. Error Management Recover: def divide(a: Int, b: Int): UIO[Int] =

    Task(a / b).foldM(_ => UIO(0), n => UIO(n))
  30. Error Management Make defects as expected errors: val io: IO[String,

    Nothing] = IO.succeed(throw new Exception("")) .unrefine(e => s"The error is: $e")
  31. 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)
  32. 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) } }
  33. 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) } ... }
  34. How ZIO runs concurrent Effects? IO[E, A] Fiber[E, A] unsafeRun

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

    IO[Nothing,Fiber[E, A]] fork Fiber[E, A] unsafeRun
  36. 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]] ... }
  37. 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] = ??? }
  38. 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
  39. 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 ()
  40. 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 ()
  41. 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 ()
  42. Resource Management File Connection Open / close / use =

    Acquire / release / use Database Clients
  43. 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))
  44. 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]
  45. 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