ZIO from
Home
Wiem Zine Elabidine
@WiemZin
Stay Safe
Functional Effects
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.
Stay home safe and
use what you have
No Side effects
Pure Function
Trust your types
Total
Test your functions
Deterministic
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)
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")
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")
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"))
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"))
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)
}
Stay home safe and
use what you have
No Side effects
Pure Function
Trust your types
Total
Test your functions
Deterministic
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
Abstract your programs!
Describe everything in
immutable data type!
Functional Effects
Effects as values?
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)
}
Functional Program
val program: IO[Unit] = for {
event <- IO.effect(handleEvent)
user <- IO.effect(getUser(event.userId))
_ <- IO.effect(logInfo(user))
...
} yield ()
Functional Program
val program: IO[Unit] = for {
event <- IO.effect(handleEvent)
user <- IO.effect(getUser(event.userId))
_ <- IO.effect(logInfo(user))
...
} yield ()
program.unsafeRun()
Execution
Just do it!
Description
Make a plan A - Z
Be prepared!
● Control over all type of interactions
● Testability
● Refactoring
Why Functional Effects?
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)
}
ZIO
Zero dependency Scala library for asynchronous and concurrent
programming using purely functional code.
Functional Effects in ZIO
ZIO[R, E, A]
Description of a program
R
E A
Dependencies
Error Success
Functional Effects in ZIO
RIO[R, A]
Description of a program
R
Throwable A
Dependencies
Error Success
Functional Effects in ZIO
URIO[R, A]
Description of a program
R
A
Dependencies
Success
Functional Effects in ZIO
Task[A]
Description of a program
Throwable A
Error Success
Functional Effects in ZIO
IO[E, A]
Description of a program
E A
Error Success
Functional Effects in ZIO
UIO[A]
Description of a program
A
Success
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)
}
Error
Management
Error Management
Throwable
def divideM(a: Int, b: Int): Task[Int] =
Task(divide(a, b))
Task[Int]
Throwable Int
Error Management
Throwable
val throwException: Task[Nothing] =
Task(throw new Exception("sorry"))
Task[Nothing]
Throwable
Error Management
String
val failedIO: IO[String, Nothing] = IO.fail("sorry again")
IO[String, Nothing]
String
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] = ???
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 ()
Error Management
The state of the program
val program: IO[Error, Unit] = ???
val programState: IO[Nothing, Exit[Error, Unit]] = program.run
Error Management
Exit[E, A]
Success[A]Failure[E]
Error Management
Exit[E, A]
Success[A]Failure[E]
Error Management
Exit[E, A]
Success[A]Failure[E]
Cause[E]
Cause[E]
Die
Expected Error
Unexpected Error, Exception
Fail[E]
Interrupted effect
Interrupt
Many causes?
Both(left, right)
Both(Fail(e1),
Then(Both(Fail(e2), Die(t)), Interrupt)
Then(left, right)
Example: Cause.Both
IO.fail("error1")
.zipPar(
IO.succeed(throw new Exception("💣 surprise!"))
)
Example: Cause.Both
IO.fail("error1") // Cause.Fail("error1")
.zipPar(
IO.succeed(throw new Exception("💣 surprise!"))
)
Example: Cause.Both
IO.fail("error1") // Cause.Fail("error1")
.zipPar(
IO.succeed(throw new Exception("💣 surprise!")) // Cause.Die(...💣)
)
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!)))
Example: Cause.Then
IO.fail("error").ensuring(IO.die(new Exception("Don't try this at Home")))
Example: Cause.Then
IO.fail("error").ensuring(IO.die(new Exception("Don't try this at Home")))
Fail("error")
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)
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))
Error Management
Expose all causes:
def divide(a: Int, b: Int): IO[Cause[Throwable], Int] =
Task(a / b).sandbox
Error Management
Catch Errors:
def divide(a: Int, b: Int): Task[Int] =
Task(a / b)
.catchSome{
case _: ArithmeticException => UIO(0)
}
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"))
}
Error Management
Fallback:
val io1: IO[Error, Int] = ???
val io2: IO[String, Int] = ???
val result: IO[String, Int] = io1.orElse(io2)
Error Management
Fallback:
val loginUser: IO[Error, Profile] = ???
val loginAnonymous: IO[Throwable, LimitedProfile] = ???
val result: IO[Throwable, Either[Profile, LimitedProfile]] =
loginUser.orElseEither(loginAnonymous)
Error Management
Recover:
def divide(a: Int, b: Int): UIO[Int] =
Task(a / b).foldM(_ => UIO(0), n => UIO(n))
Error Management
Crash it:
def divide(a: Int, b: Int): UIO[Int] =
Task(a / b).orDie
Error Management
Make defects as expected errors:
val io: IO[String, Nothing] =
IO.succeed(throw new Exception("💣"))
.unrefine(e => s"The error is: $e")
Error Management
What about fatal Errors?
Error Management
def simpleName[A](c: Class[A]) = c.getSimpleName
object Example
Task(simpleName(Example.getClass))
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)
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)
}
}
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)
}
...
}
Build concurrent
programs
ZIO Fibers
Fibers are lightweight mechanism of concurrency.
OS Thread
ZIO Fiber
Task1 Task2 Task3 ... ..
How ZIO runs Effects?
ZIO[R, E, A]
How ZIO runs Effects?
ZIO[R, E, A]
R => IO[E, A]
How ZIO runs Effects?
ZIO[R, E, A]
R => IO[E, A]
How ZIO runs Effects?
ZIO[R, E, A]
R => IO[E, A]
How ZIO runs Effects?
IO[E, A]
How ZIO runs Effects?
IO[E, A]
Fiber[E, A]
unsafeRun
How ZIO runs Effects?
IO[E, A]
Fiber[E, A]
unsafeRun
How ZIO runs Effects?
IO[E, A]
Fiber[E, A]
unsafeRun
How ZIO runs concurrent Effects?
IO[E, A]
Fiber[E, A]
unsafeRun
IO[Nothing,Fiber[E, A]]
fork
How ZIO runs concurrent Effects?
IO[E, A]
Fiber[E, A]
unsafeRun
IO[Nothing,Fiber[E, A]]
fork
How ZIO runs concurrent Effects?
IO[E, A]
Fiber[E, A]
unsafeRun
IO[Nothing,Fiber[E, A]]
fork
Fiber[E, A]
unsafeRun
How ZIO runs concurrent Effects?
IO[E, A]
Fiber[E, A]
unsafeRun
IO[Nothing,Fiber[E, A]]
fork
Fiber[E, A]
unsafeRun
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]]
...
}
Concurrent Tasks
def getUserInfo(id: Id): Task[(User, Profile)] =
fetchUser(id).zipPar(fetchProfile(id))
Task 1 Task 2
(User, Profile)
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] = ???
}
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
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 ()
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 ()
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 ()
Resource
Management
Resource Management
File Connection
Open / close / use = Acquire / release / use
Database Clients
Resource Management
IO.effect(startApp)
.ensuring(
console.putStr("Shutdown ..."))
ensuring
01
Resource Management
IO.effect(startApp)
.ensuring(
console.putStr("Shutdown ..."))
createClient(config)
.bracket(_.close)(c =>
processEvents(c))
ensuring bracket
01 02
Resource Management
IO.effect(startApp)
.ensuring(
console.putStr("Shutdown ..."))
ensuring
val resource =
Managed.make(openFile(file))(_.close)
...
resource.use(computeLines)
Managementbracket
01 0302
createClient(config)
.bracket(_.close)(c =>
processEvents(c))
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]
ZIO
is Awesome
WrapUp
documentation
https://github.com/zio/zio
zio.dev
ZIO projects
Blog post
https://medium.com/@wiemzin
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

Zio from Home