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:
|
|
The 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 putStrLn
and getStrLn
.
If you integrate ZIO into an existing application, create a runtime and run ZIO code.
|
|
The Task(println("Hi))
creates a ZIO effect that wraps () => println("Hi")
. ZIO runtime executes this partial function in a fiber.
2 Background
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
- testability
- (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 [A]
. The 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.
4 The for
Expression
A better way to write the functional effect program is using the for
expression. It means that you need to define flatMap
and 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 flatMap
and map
method to the Console[A]
.
|
|
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 flatMap
method, 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. map
and 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 failsforever
: repeat forever or faileventually
: repeat until sucessflip
: switch error and value and brings many possibilities, for example, can be used to implementmapError
, implementeventually
usingforever
, implement a fall back planfromTry
,fromOption
,fromEither
,fromFuture
to 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.