This part is based on the Overview Document of ZIO and the video of A Tour of ZIO. ZIO is a library for asynchronous and concurrent programming based on pure functional programming model. It is primarily not an IO effect library!
A key concept in ZIO is that you describe the values and functions using the type
ZIO[R, E, A]. A value of
ZIO[R, E, A] is an immutable data structure representing a function of type
R => Either[E, A]. It is not just the low level functions, the whole program is also represented by such a value.
Once you use the ZIO type, you stay with it and use its methods to map, chain, etc to work with it. Then the feature is completely described, you use
prodive method to provide the
R and make it a runnable type of
IO. Finally you run it with
1.1 The Basci
ZIO[-R, +E, +A] has three type parameters:
R: represents a requirement of an environment of type
R. If this type parameter is
Any, it means the effect has no requirements because it can be an
(). Example values are
- Database connection, repository
- Web Service, logging service
- Application context, clock, random generator
- Configuration, session data
E: a failure type representing a faile of type
E. Often it is
Throwable. If the type is
Nothing, it means the effect cannot fail because there is no value of type
Nothing. Example values are
A: a success type of
A. If it is
Unit, it means that the program is side-effect-only program that produces no useful information. If it is
Nothing, it means the program runs forever or until it fails.
ZIO[R, E, A] must have an
A type, the
E are optional. There are three possible aliases:
ZIO[Any, Nothing, A], an effect has no requirements and cannot fail. To help remember, you can think that it represents an
Unit IOthat turns a unit value into an effect. A unit value cannot fail.
URIO[R, A]: is
ZIO[R, Nothing, A], an effect has requirments but cannot fail. It wraps a function that takes an environment
Rand produces a value of type
A. The wrapped function is a total function that cannot fail.
IO[E, A]: is
ZIO[Any, E, A], an effect that doesn’t depend on any environment.
There are two aliase if
IO[Throwable, A], a special subtype of
IO[E, A]that can throw.
RIO[R, A]: is
ZIO[R, Throwable, A], a subtype of
ZIO[R, E, A]can throw error.
Therefore there are total six types:
ZIO[R, E, A],
URIO[R, A], because
R can be
E can be
Throable. Each type defines methods to create values of the type.
Task is similar to
Future that can throw or return data.
UIO represents an infallible effects, including those resulting from handling all errors.
The relationship is:
ZIO –> IO –> Task–> UIO ZIO –> RIO –> URIO –> UIO
Some examples from the ZIO document:
2 Creating Effects
The blog Wrapping impure code with ZIO has some examples.
fromFiberM to produce an
IO[E, A]. These methods either provide a
R or transform
SomeType[E, A] to
fromFunctionFuture produce a
There are three ways to create a
UIO[A] effect in
def effectTotal[A](effect: => A): UIO[A] = new ZIO.EffectTotal(() => effect)
def succeed[A](a: => A): UIO[A] = effectTotal(a)
def some[A](a: => A): UIO[Option[A]] = succeed(Some(a))
In addition to
Task all have the
ZIO[R, E, A]
There are dozens of ZIO methods that create
ZIO[R, E, A]. That’s the most common result of a ZIO operation.
2.7 From Values
- Success values:
- Failure values:
- Scala values:
- Function values:
2.8 Sync and Async
A synchronous side-effect can be converted into a ZIO effect using
ZIO.effect, for example,
val getStrinLn: Task[String] = ZIO.effect(scala.io.StdIn.readLine()). Like Future, side-effect only throw exceptions. If a side-effect doesn’t throw any exception, use
ZIO.effectTotal, for example
def putStrLn(line: String): UIO[Unit] = ZIO.effectTotal(println(line)).
An asynchronous side-effect with a callback-based API can be converted into a ZIO effect using
effectAsyncMabye methods of
ZIO. It returns a value of type
ZIO[R, E, A] that has features such as interruption, resource-safety and good error-handling. Here is an example:
2.9 Blocking Code
zio.blocking package for blocking IO such as file or network calls. The
effectBlock(Thread.sleep(10)) will be executed on a separate thread pool. Use
effectBlockingCancelable for cancelable side-effects. The
blocking method is used to run ZIO effect in the blocking thread pool that may increase to a big number of threads.
3 Basic Operations
A ZIO effect provides many methods to process data or compose more effects.
map: transform success value. The shortcut for this is
mapErrorCause: transform failure value.
orElse: use an other effect when the first fails.
fold: handles both failure and success results.
catchAllCause: handle all errors.
flatMap: the result of the first effect is the input of the second effect. When the first effect fails, the
forexpression: chain multiple effects using
zip: the first executes first, then the second, the results are zipped into a tuple. If either fails, the cmposed effect fails.
*>, only keep the right.
<*, only keep the left.
ZIO#timeout method covnerts an effect into
Option[A]. When it completes within the timeout, the result is
4 Handling Errors
ZIO provides full stack trace of errors. It gives the location of error, the next statement to be executed and many other useful informaiton.
You can surface failure with
either that takes an
ZIO[R, E, A] to
ZIO[R, Nothing, Eiterh[E, A]]. The result is the same as
URIO[R, Either[E, A]]. You can submerge failures with
ZIO.absovle that transform an
URIO[R, Either[E, A]] into
ZIO[R, E, A].
catchAll to recover from all types of errors. Use
catchSome with a partial function to recover from some types of errors. Both cannot rececude or eliminate the error type, they can wide the error type to a subtype. Use
orElse to try another effect when the first fails.
fold method lets you define non-effectful handler for failure and success. The
foldM method lets your handle both in a effectful way.
retry method teaks a
Schedule and returns a new effect that will retry the first effect if it fails.
retryOrElse allows both retry and, if all retries fail, try another effect.
retryOrElseEither allows returning a differen type for the fallback.
5 Handling Resources
ZIO’s resource management provides guarantees in the presence of failure, interruption or defects in the application.
ensuring(finalizer) guarantees that if an effect terminates for whatever reason, the finalizer will begin executing. The finalizer has a type of
UIO[A] and is not allowed to fail. It must handle all errors internally.
ensuring works across all types of effects, including asynchronous and concurrent effects.
bracket method takes a release effect and a use effect. It guarantees to run to run the release effect, even in the presence of errors or interruption.
6 Basic Concurrency
ZIO provides concurrency via fibers. Fibers are low level. ZIO provides high-level operations built on fibers.
ZIO fibers consume almost no memory, have growable and shrinkable stacks, don’t waste resources blocking, and will be garbage collected automatically if they are suspended and unreachable.
Fibers are created and scheduled by ZIO runtime and cooperatively yield to each other. All effects are executed by some fibers. A fiber type
Fiber[E, A] models an effect that is running.
E is the failure type and
A is the success value. A fiber represents a handle on the running computaiton.
fork method creates a new fiber and execute the effect on this new fiber. The
Fiber#join returns the result
Fiber#await returns an effect containing an
Exit value that provides full information on how the fiber completed.
Fiber#interrupt intterrupts the fiber and returns an
Exit. By design, the effect returned by
Fiber#interrupt does not resume until the fiber has completed. If this is not desired, you can
fork the interruption as
_ <- fiber.interrupt.fork.
Exit[E, A] describes the result of executing an
IO value. The result is either succeeded with a value
A, or failed with a
Fiber#zipWith compose fibers. The
Fiber.orElse runs the second fiber if the first fails.
ZIO provides parallel operations. These methods are named with a
Par suffix. For example,
mergeAllPar. If one fails, the others will be interrupted. If this is undesired, convert fallible effects into infallible effects using the
For first success, use
fiber1 race fiber2. If the first success or failure, use
fiber1.either race fiber2.either.
7 Running Effects
For a greenfield project, extends
zio.APP and define
def run(args: List[String]): ZIO[ZEnv, Nothing, Int]. The
type ZEnv = Clock with Console with System with Random with Blocking is a resource that is provided by the default runtime:
zio.Runtime.default. It can run effects that require any combination of these modules. The following is a simple application:
An other way to use the default runtime is to use it to run logics directly without using
zio.App. Foe example:
A custom runtime
Runtime[R] can be created with two values:
Platform: to bootstrap the runtime system
The following code creates a
Runtime that provides an
Int to effects:
Platform has an error report that can be customized. The default is to log the error to standard error.