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!
1 The ZIO
Type
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 runtime.unsafeRun(runable)
.
1.1 The Basci ZIO
Type
The ZIO[-R, +E, +A]
has three type parameters:
R
: represents a requirement of an environment of typeR
. If this type parameter isAny
, it means the effect has no requirements because it can be anUnit
value()
. 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 typeE
. Often it isThrowable
. If the type isNothing
, it means the effect cannot fail because there is no value of typeNothing
. Example values are- Throwable
- IOExceptions
- DBError
- MyAppError
- Unit
A
: a success type ofA
. If it isUnit
, it means that the program is side-effect-only program that produces no useful information. If it isNothing
, it means the program runs forever or until it fails.
1.2 Aliases
Because the ZIO[R, E, A]
must have an A
type, the R
or E
are optional. There are three possible aliases:
UIO[A]
: isZIO[Any, Nothing, A]
, an effect has no requirements and cannot fail. To help remember, you can think that it represents anUnit IO
that turns a unit value into an effect. A unit value cannot fail.URIO[R, A]
: isZIO[R, Nothing, A]
, an effect has requirments but cannot fail. It wraps a function that takes an environmentR
and produces a value of typeA
. The wrapped function is a total function that cannot fail.IO[E, A]
: isZIO[Any, E, A]
, an effect that doesn’t depend on any environment.
There are two aliase if E
is Throwable
:
Task[A]
: isIO[Throwable, A]
, a special subtype ofIO[E, A]
that can throw.RIO[R, A]
: isZIO[R, Throwable, A]
, a subtype ofZIO[R, E, A]
can throw error.
Therefore there are total six types: ZIO[R, E, A]
, IO[E, A]
, RIO[R, A]
, UIO[A]
, Task[A]
, and URIO[R, A]
, because R
can be R
or Any
, and E
can be E
, Nothing
or 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
1.3 Examples
Some examples from the ZIO document:
|
|
2 Creating Effects
The blog Wrapping impure code with ZIO has some examples.
2.1 Creating IO[E, A]
ZIO
methods provide
, done
, fromEither
, fromFiber
and fromFiberM
to produce an IO[E, A]
. These methods either provide a R
or transform SomeType[E, A]
to IO[E, A]
.
2.2 Creating RIO[R, A]
ZIO
methods effectSuspend
, effectSuspendWith
, and fromFunctionFuture
produce a RIO[R, A]
.
2.3 Creating UIO[A]
There are three ways to create a UIO[A]
effect in ZIO
:
ZIO.effectTotal
:def effectTotal[A](effect: => A): UIO[A] = new ZIO.EffectTotal(() => effect)
ZIO.succeed
:def succeed[A](a: => A): UIO[A] = effectTotal(a)
ZIO.some
:def some[A](a: => A): UIO[Option[A]] = succeed(Some(a))
In addition to UIO.apply
method, IO
, RIO
, UIO
, URIO
and Task
all have the effectTotal
, succeed
and some
methods.
2.4 Creating Task[A]
ZIO
methods:apply
,effect
,fromFuture
,fromFutureInterrupt
,fromTry7
andgetOrFail
.Task
methods
2.5 Creating URIO[R, A]
ZIO
methods:either
,ensuring
,eventually
,fold
,foldCause
,orDie
,orDieWith
,fromFunction
,environment
,ignore
,merge
,orElseSucceed
,option
,fork
,forkAs
,forkDaemon
,forkAll
,forkWithErrorHandler
,forkAll_
,raceAll
,to
,toFuture
,toFutureWith
,collectAllSuccesses
,collectAllSuccessesPar
,collectAllSuccessesParN
,environment
,fromFunction
,runTime
,URIO
methods
2.6 Creating 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:
ZIO.succeed
,ZIO.effectTotal
,ZIO.effect
etc - Failure values:
ZIO.fail
,Task.fail
- Scala values:
ZIO.fromOption
,ZIO.fromEither
,ZIO.fromTry
- Function values:
ZIO.fromFunction
- Future:
ZIO.fromFuture
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 effectAsync
, effectAsyncInterrupt
, effectAsyncM
, and 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 provides 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 isas
.mapError
ormapErrorCause
: transform failure value.orElse
: use an other effect when the first fails.fold
: handles both failure and success results.catchAll
orcatchAllCause
: handle all errors.flatMap
: the result of the first effect is the input of the second effect. When the first effect fails, theflatMap
doesn’t run.for
expression: chain multiple effects usingflatMap
andmap
.zip
: the first executes first, then the second, the results are zipped into a tuple. If either fails, the cmposed effect fails.zipRight
andzipLeft
:zipRight
is*>
, only keep the right.zipLeft
is<*
, only keep the left.
The ZIO#timeout
method covnerts an effect into Option[A]
. When it completes within the timeout, the result is Some[A]
, otherwise, None
.
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]
.
Use 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.
The fold
method lets you define non-effectful handler for failure and success. The foldM
method lets your handle both in a effectful way.
The 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.
The 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.
The 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.
The fork
method creates a new fiber and execute the effect on this new fiber. The Fiber#join
returns the result IO[E, A]
.
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
.
An Exit[E, A]
describes the result of executing an IO
value. The result is either succeeded with a value A
, or failed with a Cause[E]
.
Fiber#zip
and 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, zipPar
, zipWithPar
, collectAllPar
, foreachPar
, reduceAllPar
, and mergeAllPar
. If one fails, the others will be interrupted. If this is undesired, convert fallible effects into infallible effects using the ZIO#either
or ZIO#option
methods.
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 run
method 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:
R
: theEnvironment
Platform
: to bootstrap the runtime system
The following code creates a Runtime
that provides an Int
to effects:
|
|
The Platform
has an error report that can be customized. The default is to log the error to standard error.