This is a study note of ZIO: a library for asynchronous and concurrent programming based on pure functional programming. This part covers the getting started and motivation of ZIO. It is based on Getting Started, the background, zio history and the video of Magic Tricks with Functional Effects.
1 Getting Started
Include ZIO in
build.sbt file as
libraryDependencies += "dev.zio" %% "zio" % "1.0.0-RC18-2".
The application can extend
zio.App, which provides a runtime. The code is as following:
run method should return an unexceptional ZIO value. An unexceptional ZIO value is a ZIO value that has all its errors handled. The
fold method handles both error and succss values to 1 and 0 correspondingly. The
zio.console module provides IO methods such as
If you integrate ZIO into an existing application, create a runtime and run ZIO code.
Task(println("Hi)) creates a ZIO effect that wraps
() => println("Hi"). ZIO runtime executes this partial function in a fiber.
Procedural functions have three problems:
- partial: throw exception in some input
- non-deterministic: may return different values for the same input
- impure: perform side effects that mutate data or interact with the external world
A pure function is total, deterministic and pure. Pure functions are easier to understand, easier to test, easier to refactor, and easier to abstract over. Functional programs construct and return data structures that describe interaction with the real world. Immutable data structures that model side-effects are called
functional effects or simply “effects” in ZIO. ZIO let you build programs using the functional/lazy approach: the idea is to describe all side-effects using an immutable data structure and make the program a pure functional program. You can refactor, abstract and test the program easily because it is easy to understand the pure functional code. To run it, interprete the data structure and execute.
ZIO introduces a new way to do functional programming in Scala with following features:
- improved operation names, no FP jargons such as monda, pure etc.
- async/concurrent constructs: fiber, STM, Ref
- depency declarations
- resource management
- (all is done with) no type classes, no implicits, no higher-kinded types, and no category theory
3 An Example
A simple console program can be modeled by the following types:
You can think that the
Console[+A] is a program that returns a value of type
Return case class describes a side-effect that takes no input and return a value of type
A. It represents the completion of the program and should be the last element of the program. The
PrintLine has a line to be printed and the rest of the program. The
ReadLine reads a line and use it as an input to the rest of the program. Following is an interpret to execute a program consisting of the three case classes.
The following is an execution of a sample program.
The functional effect program is a recursive data structure that ends with
Return. The return could use a by-name parameter to deliver program completion status. Because a case class cannot have a by-name parameter, the example uses a functional parameter
() => A for simplicity.
A better way to write the functional effect program is using the
for expression. It means that you need to define
map method for
Console[A]. Meanwhile, each generator expression in the
for should create a
Console[A] instance. First, create some help methods and then define an implicit class that adds
map method to the
Execute a program with the following code:
The above code explains the “theory” behind ZIO library: writing program as a functional effect to represent the logic, then interprete and execute the code. The function effect is an immutable, type-safe, tree-like data structure that models side effects. The benefits are equational reasoning, composability and type safety, as results of functioanl programming.
5 The Toy ZIO
Functional effects interact with the outside world in a way that is composable, type-safe and testable. It describes the interaction as immutable values and executed at the edage of the program.
A value of type
ZIO[R, E, A] is an effectful versoin of
R => Either[E, A]. The mental model of ZIO is as the following, based on the video of Magic Tricks with Functional Effects:
An instance of
ZIO[R, E, A] wraps a function that takes an
R input and produces a value of type
Either[E, A]. ZIO effects are not actually functions because they model complex effects like asynchronous and concurrent effects. As shown in the
ZIO is implemented as a state monad. In the
flatMap composition, the second (inside) effect can use the result of the first (enclosing) effect in the happy path.
flatMap model the essence of the imperative programming composition that executes functions sequentially: one for pure function (
A => B) and one for effectful function (
A => ZIO[R, E, B]).
6 Other Effects and Magic Tricks
The above only shows the success effects, failure effects and synchronous effects. There are asynchronous effects, concurrent effects, cancelation effects, resource effects, paralle effects and dependency effects. The combination of these effects brings many magic tricks such as:
orElse: try an alternative when the first fails
forever: repeat forever or fail
eventually: repeat until sucess
flip: switch error and value and brings many possibilities, for example, can be used to implement
forever, implement a fall back plan
fromFutureto unify different Scala effects.
Other tricks include interruptable and auto-collectable fibers, auto-retry and transactional STM that involves multiple operations and multiple shared data. ZIO gives rich debuggable messages and fiber dumps.
The painpoints of ZIO include combination of errors, currently only super type that may lose information. May use union type of Scala 3. Another pain point is the environment combination like A with B for proxies, Scala 3 may have keyword support like Kotlin.