Skip to main content
Version: 2.x

Creating Effects

This section explores some of the common ways to create ZIO effects from values, from computations, and from common Scala data types.

From Values

Using the ZIO.succeed method, you can create an effect that, when executed, will succeed with the specified value:

val s1 = ZIO.succeed(42)

The succeed method takes a so-called by-name parameter, which ensures that if you pass the method some code to execute, that this code will be stored inside the ZIO effect so that it can be managed by ZIO, and benefit from features like retries, timeouts, and automatic error logging.

From Failure Values

Using the ZIO.fail method, you can create an effect that, when executed, will fail with the specified value:

val f1 = ZIO.fail("Uh oh!")

For the ZIO data type, there is no restriction on the error type. You may use strings, exceptions, or custom data types appropriate for your application.

Many applications will model failures with classes that extend Throwable or Exception:

val f2 = ZIO.fail(new Exception("Uh oh!"))

From Scala Values

Scala's standard library contains a number of data types that can be converted into ZIO effects.

Option

An Option can be converted into a ZIO effect using ZIO.fromOption:

val zoption: IO[Option[Nothing], Int] = ZIO.fromOption(Some(2))

The error type of the resulting effect is Option[Nothing], signifying that if such an effect fails, it will fail with the value None (which has type Option[Nothing]).

You can transform a failure into some other error value using orElseFail, one of many methods that ZIO provides for error management:

val zoption2: ZIO[Any, String, Int] = zoption.orElseFail("It wasn't there!")

ZIO has a variety of other operators designed to make interfacing with Option code easier. In the following advanced example, the operators some and asSomeError are used to make it easier to interface with methods returning Option, similar to the OptionT type in some Scala libraries.

val maybeId: ZIO[Any, Option[Nothing], String] = ZIO.fromOption(Some("abc123"))
def getUser(userId: String): ZIO[Any, Throwable, Option[User]] = ???
def getTeam(teamId: String): ZIO[Any, Throwable, Team] = ???


val result: ZIO[Any, Throwable, Option[(User, Team)]] = (for {
id <- maybeId
user <- getUser(id).some
team <- getTeam(user.teamId).asSomeError
} yield (user, team)).unsome

Either

An Either can be converted into a ZIO effect using ZIO.fromEither:

val zeither: ZIO[Any, Nothing, String] = ZIO.fromEither(Right("Success!"))

The error type of the resulting effect will be that of the Left case, while the success type will be that of the Right case.

Try

A Try value can be converted into a ZIO effect using ZIO.fromTry:

import scala.util.Try

val ztry = ZIO.fromTry(Try(42 / 0))

The error type of the resulting effect will always be Throwable because Try can only fail with values of type Throwable.

Future

A Scala Future can be converted into a ZIO effect using ZIO.fromFuture:

import scala.concurrent.Future

lazy val future = Future.successful("Hello!")

val zfuture: ZIO[Any, Throwable, String] =
ZIO.fromFuture { implicit ec =>
future.map(_ => "Goodbye!")
}

The function passed to fromFuture is provided an ExecutionContext, which allows ZIO to manage where the Future runs (of course, you can ignore this ExecutionContext).

The error type of the resulting effect will always be Throwable, because Future values can only fail with values of type Throwable.

From Code

ZIO can convert any code (such as a call to some method) into an effect, whether that code is so-called synchronous (directly returning a value), or asynchronous (passing a value to callbacks).

If done properly, when you convert code into a ZIO effect, this code will be stored inside the effect so that it can be managed by ZIO, and benefit from features like retries, timeouts, and automatic error logging.

The conversion functions that ZIO has allow you to seamlessly use all features of ZIO with non-ZIO code written in Scala or Java, including third-party libraries.

Synchronous Code

Synchronous code can be converted into a ZIO effect using ZIO.attempt:

import scala.io.StdIn

val readLine: ZIO[Any, Throwable, String] =
ZIO.attempt(StdIn.readLine())

The error type of the resulting effect will always be Throwable, because synchronous code may throw exceptions with any value of type Throwable.

If you know for a fact that some code does not throw exceptions (except perhaps runtime exceptions), you can convert the code into a ZIO effect using ZIO.succeed:

def printLine(line: String): UIO[Unit] =
ZIO.succeed(println(line))

Sometimes, you may know that code throws a specific exception type, and you may wish to reflect this in the error parameter of your ZIO effect.

For this purpose, you can use the ZIO#refineToOrDie method:

import java.io.IOException

val readLine2: ZIO[Any, IOException, String] =
ZIO.attempt(StdIn.readLine()).refineToOrDie[IOException]

Asynchronous Code

Asynchronous code that exposes a callback-based API can be converted into a ZIO effect using ZIO.async:

object legacy {
def login(
onSuccess: User => Unit,
onFailure: AuthError => Unit): Unit = ???
}

val login: ZIO[Any, AuthError, User] =
ZIO.async[Any, AuthError, User] { callback =>
legacy.login(
user => callback(ZIO.succeed(user)),
err => callback(ZIO.fail(err))
)
}

Asynchronous effects are much easier to use than callback-based APIs, and they benefit from ZIO features like interruption, resource-safety, and error management.

Blocking Synchronous Code

Some synchronous code may engage in so-called blocking IO, which puts a thread into a waiting state, as it waits for some operating system call to complete. For maximum throughput, this code should not run on your application's primary thread pool, but rather, in a special thread pool that is dedicated to blocking operations.

ZIO has a blocking thread pool built into the runtime, and lets you execute effects there with ZIO.blocking:

import scala.io.{ Codec, Source }

def download(url: String) =
ZIO.attempt {
Source.fromURL(url)(Codec.UTF8).mkString
}

def safeDownload(url: String) =
ZIO.blocking(download(url))

As an alternative, if you wish to convert blocking code directly into a ZIO effect, you can use the ZIO.attemptBlocking method:

val sleeping =
ZIO.attemptBlocking(Thread.sleep(Long.MaxValue))

The resulting effect will be executed on ZIO's blocking thread pool.

If you have some synchronous code that will respond to Java's Thread.interrupt (such as Thread.sleep or lock-based code), then you can convert this code into an interruptible ZIO effect using the ZIO.attemptBlockingInterrupt method.

Some synchronous code can only be cancelled by invoking some other code, which is responsible for canceling the running computation. To convert such code into a ZIO effect, you can use the ZIO.attemptBlockingCancelable method:

import java.net.ServerSocket
import zio.UIO

def accept(l: ServerSocket) =
ZIO.attemptBlockingCancelable(l.accept())(ZIO.succeed(l.close()))

Next Steps

If you are comfortable creating effects from values, converting from Scala types into effects, and converting synchronous and asynchronous code into effects, the next step is learning basic operations on effects.