Skip to content

Default ZIO Services

ZIO provides four different default services for all applications:

  1. Clock. Provides functionality related to time and scheduling. If you are accessing the current time or scheduling a computation to occur at some point in the future you are using this.
  2. Console. Provides functionality related to console input and output.
  3. System. Provides functionality for getting system and environment variables.
  4. Random. Provides functionality for generating random values.

Because this essential system functionality is provided as services, you can trivially test any code that uses these services, without actually interacting with production implementations.

For example, the Random service allows us to generate random numbers. The Live implementation of that service just delegates to scala.util.Random. But that may not always be the implementation we want. scala.util.Random is non-deterministic, which can be useful in production but can make it harder for us to test our programs.

For testing the random service, we may want to use a purely functional pseudo-random number generator, which always generates the same values given the same initial seed. This way, if a test fails, we can reproduce the failure and debug it.

ZIO Test, a toolkit for testing ZIO applications that we will discuss in a later chapter, provides a TestRandom that does exactly this. In fact, ZIO Test provides a test implementation of each of the standard services, and you can imagine wanting to provide other implementations, as well.

By defining functionality in terms of well-defined interfaces, we defer concrete implementations until later. As you will see, using these services with ZIO is very easy, but at the same time, power users have tremendous flexibility to provide custom implementations of these services (or those you define in your own application).

Now let’s discuss each of the standard ZIO services in some detail.

The Clock service provides functionality related to time and scheduling. This includes several methods to obtain the current time in different ways (currentTime to return the current time in the specified TimeUnit, currentDateTime to return the current OffsetDateTime, and nanoTime to obtain the current time in nanoseconds).

In addition, the Clock service includes a sleep method, which can be used to sleep for a certain amount of time.

The signature of nanoTime and sleep are shown in the following snippet:

object Clock {
def nanoTime: ZIO[Any, Nothing, Long]
def sleep(duration: => Duration): URIO[Any, Nothing, Unit]
}

The sleep method is particularly important. It does not complete execution until the specified duration has elapsed, and like all ZIO operations, it is non-blocking, so it doesn’t actually consume any threads while it is waiting for the time to elapse.

We could use the sleep method to implement the delay operator that we saw earlier in this chapter:

def delay[R, E, A](zio: ZIO[R, E, A])(
duration: Duration
): ZIO[R, E, A] =
Clock.sleep(duration) *> zio

The Clock service is the building block for all time and scheduling functionality in ZIO.

The Console service provides functionality around reading from and writing to the console.

So far in this book, we have been interacting with the console by converting procedural code in the Scala library to ZIO effects, using the ZIO.attempt constructor. This was useful in illustrating how to translate procedural to ZIO and demonstrating that there is no “magic” in ZIO’s own console facilities.

However, wrapping console functionality directly is not ideal because we cannot provide alternative implementations for testing environments. In addition, there are some tricky edge cases for console interaction that the Console service handles for us. (For example, reading from the console can fail only with an IOException.)

The key methods on the Console service are Console.readLine, which is analogous to readLine() and Console.printLine, which is the equivalent of println. There is also a print method if you do not want to add a new line after printing text to the console.

object Console {
val readLine: ZIO[Any, IOException, String]
def print(line: => String): ZIO[Any, Nothing, Unit]
def printLine(line: => String): ZIO[Any, Nothing, Unit]

The Console service is commonly used in console applications but is less common in generic code than Clock or Random.

In the rest of this book, we will illustrate examples involving console applications with these methods rather than converting methods from the Scala standard library.

The System service provides functionality to get system and environment variables:

object System {
def env(variable: String): ZIO[Any, SecurityException, Option[String]]
def property(prop: String): ZIO[Any, Throwable, Option[String]]

The two main methods on the System service are System.env, which accesses a specified environment variable, and System.property, which accesses a specified system property. There are also other variants for obtaining all environment variables or system properties or specifying a backup value if a specified environment variable or property does not exist.

Like the Console service, the System service tends to be used more in applications or certain libraries (e.g., those dealing with configuration) but is uncommon in generic code.

The Random service provides functionality related to random number generation. The Random service exposes essentially the same interface as scala.util.Random, but all the methods return functional effects. So, if you’re familiar with code like random.nextInt(6) from the standard library, you should be very comfortable working with the Random service.

The Random service is sometimes used in generic code in scheduling, such as when adding a random delay between recurrences of some effect.