Skip to main content
Version: 2.0.x

Basic Operations

Like the String data type, as well as the collection data types in Scala (such as List, Map, and Set), ZIO effects are immutable, and cannot be changed.

In order to transform or combine ZIO effects, you can use the methods on the ZIO data type, which return new effects, with the specified transformations or combinations applied to them.

There are two categories of methods on the ZIO data type:

  • Transformations. Transformation functions alter an effect in some well-defined way, allowing you to customize runtime behavior. For example, calling effect.timeout(60.seconds) on an effect returns a new effect, which, when executed, will apply a timeout to the original effect.
  • Combinations. Combination functions combine two or more effects together in a single effect. For example, calling effect1.orElse(effect2) combines two effects in such a fashion that the returned effect, when executed, will first execute the left hand side, and if that fails, it will then execute the right hand side. This lets you specify a fallback effect in case a primary effect fails.

Mapping

If you have an effect that succeeds with some value, you can use ZIO#map to obtain a new effect, which will transform the value using the function you provide.

import zio._

val succeeded: ZIO[Any, Nothing, Int] = ZIO.succeed(21).map(_ * 2)

In a similar fashion, you can transform an effect that has one error to an effect with a different error using the ZIO#mapError method, which requires you supply a function to do the conversion:

val failed: ZIO[Any, Exception, Unit] = 
ZIO.fail("No no!").mapError(msg => new Exception(msg))

Note that mapping the error or success value of an effect does not change whether or not the effect fails or succeeds. This is similar to how mapping over Scala's Either data type does not change whether the Either is Left or Right.

Chaining

You can execute two effects sequentially with the flatMap method. The flatMap method requires that you pass a callback, which will receive the success value of the first effect, and must return a second effect, which depends on this value:

val sequenced: ZIO[Any, IOException, Unit] =
Console.readLine.flatMap(input => Console.printLine(s"You entered: $input"))

If the first effect fails, the callback passed to flatMap will never be invoked, and the effect returned by flatMap will also fail.

In any chain of effects created with flatMap, the first failure will short-circuit the whole chain, just like throwing an exception will prematurely exit a sequence of statements.

For Comprehensions

Because the ZIO data type supports both flatMap and map, you can use Scala's for comprehensions to build imperative effects:

val program: ZIO[Any, IOException, Unit] =
for {
_ <- Console.printLine("Hello! What is your name?")
name <- Console.readLine
_ <- Console.printLine(s"Hello, ${name}, welcome to ZIO!")
} yield ()

For comprehensions provide a procedural syntax for creating chains of effects, and are the fastest way for most programmers to get up to speed using ZIO.

Zipping

You can combine two effects into a single effect with the ZIO#zip method.

The method returns an effect that will execute the left effect first, followed by the right effect, and which will place both success values into a tuple:

val zipped: ZIO[Any, Nothing, (String, Int)] = 
ZIO.succeed("4").zip(ZIO.succeed(2))

In any zip operation, if either the left or right-hand side fails, the composed effect will fail, because both values are required to construct the tuple. If the left side fails, the right side will not be executed at all.

Sometimes, when the success value of an effect is not useful (for example, if it is Unit), it can be more convenient to use the ZIO#zipLeft or ZIO#zipRight functions, which first perform a zip and then map over the tuple to discard one side or the other:

val zipRight1: ZIO[Any, IOException, String] =
Console.printLine("What is your name?").zipRight(Console.readLine)

The zipRight and zipLeft functions have symbolic aliases, known as *> and <*, respectively. Some developers find these operators easier to read:

val zipRight2: ZIO[Any, IOException, String] =
Console.printLine("What is your name?") *>
Console.readLine

Next Steps

If you are comfortable with the basic operations on ZIO effects, the next step is to learn about error handling.