scalaadvanced

ZIO Effect System Basics

Build programs with ZIO: effects, error handling, layers, and concurrent operations.

scala
import zio.*
import zio.Console.*
import java.io.IOException

object MyApp extends ZIOAppDefault:

  // ZIO[R, E, A] — requires R, can fail with E, succeeds with A
  val hello: ZIO[Any, Nothing, Unit] = Console.printLine("Hello, ZIO!").orDie

  val greeting: ZIO[Any, IOException, String] = for
    _    <- printLine("What is your name?")
    name <- readLine
    _    <- printLine(s"Hello, $name!")
  yield name

  // Error handling
  val risky: ZIO[Any, String, Int] = ZIO.fail("Something went wrong")
  val safe: ZIO[Any, Nothing, Int] = risky.catchAll { error =>
    ZIO.succeed(0) <* Console.printLine(s"Caught: $error").orDie
  }

  val folded: ZIO[Any, Nothing, String] = risky.fold(
    error   => s"Failed: $error",
    success => s"Got: $success"
  )

  // Parallel operations
  def fetchUser(id: Int): ZIO[Any, Nothing, String] =
    ZIO.sleep(100.millis) *> ZIO.succeed(s"User-$id")

  val parallel: ZIO[Any, Nothing, (String, String, String)] =
    fetchUser(1) <&> fetchUser(2) <&> fetchUser(3)

  val raced: ZIO[Any, Nothing, String] =
    fetchUser(1).delay(200.millis) race fetchUser(2).delay(100.millis)

  // Retry
  var attempt = 0
  val flaky: ZIO[Any, String, String] = ZIO.suspend {
    attempt += 1
    if attempt < 3 then ZIO.fail(s"Attempt $attempt failed")
    else ZIO.succeed("Success!")
  }

  val retried: ZIO[Any, String, String] =
    flaky.retry(Schedule.recurs(5) && Schedule.spaced(100.millis))

  // Fiber (lightweight thread)
  val fiberDemo: ZIO[Any, Nothing, Unit] = for
    fiber  <- fetchUser(1).fork
    _      <- ZIO.sleep(50.millis)
    result <- fiber.join
    _      <- Console.printLine(s"Fiber result: $result").orDie
  yield ()

  // Ref (mutable reference)
  val counterDemo: ZIO[Any, Nothing, Unit] = for
    ref    <- Ref.make(0)
    _      <- ZIO.foreachPar(1 to 100)(_ => ref.update(_ + 1))
    count  <- ref.get
    _      <- Console.printLine(s"Counter: $count").orDie
  yield ()

  // Traverse
  val ids = (1 to 5).toList
  val allUsers: ZIO[Any, Nothing, List[String]] =
    ZIO.foreach(ids)(fetchUser)
  val allPar: ZIO[Any, Nothing, List[String]] =
    ZIO.foreachPar(ids)(fetchUser)

  def run: ZIO[Any, Any, Unit] = for
    _ <- hello
    _ <- safe.debug("Safe")
    f <- folded
    _ <- Console.printLine(f).orDie
    p <- parallel
    _ <- Console.printLine(s"Parallel: $p").orDie
    r <- raced
    _ <- Console.printLine(s"Race winner: $r").orDie
    _ <- fiberDemo
    _ <- counterDemo
    u <- allPar
    _ <- Console.printLine(s"All users: $u").orDie
  yield ()

Use Cases

  • Typed error handling with ZIO
  • Concurrent fiber-based programming
  • Retry policies and scheduling

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.