Skip to content

Recursion and ZIO

We talked earlier in this chapter about using flatMap and related operators to compose effects sequentially.

Ordinarily, if you call a recursive function, and it recurses deeply, the thread running the computation may run out of stack space, which will result in your program throwing a stack overflow exception.

One of the features of ZIO is that ZIO effects are stack-safe for arbitrarily recursive effects. So, we can write ZIO functions that call themselves to implement any kind of recursive logic with ZIO.

For example, let’s say we want to implement a simple console program that gets two integers from the user and multiplies them together.

We can start by implementing an operator to get a single integer from the user, as shown in the following snippet:

import zio._
val readInt: ZIO[Any, Throwable, Int] =
for {
line <- Console.readLine
int <- ZIO.attempt(line.toInt)
} yield int

This effect can fail with an error type of Throwable, because the input from the user might not be a valid integer. If the user doesn’t enter a valid integer, we want to print a helpful error message to the user and then try again.

We can build this functionality atop our existing readInt effect by using recursion. We define a new effect, readIntOrRetry that will first call readInt. If readInt is successful, we just return the result. If not, we prompt the user to enter a valid integer and then recurse:

import java.io.IOException
lazy val readIntOrRetry: ZIO[Any, IOException, Int] =
readInt
.orElse(Console.printLine("Please enter a valid integer")
.zipRight(readIntOrRetry)
)

Using recursion, we can create our own sophisticated control flow constructs for our ZIO programs.