ZIO provides standard services for common application needs including console I/O, time operations, randomness, and system access, all following consistent patterns for testing and service substitution.
Service for console input/output operations with proper error handling and encoding support.
/**
* Service for console I/O operations
*/
trait Console {
/** Print a value to stdout */
def print(line: => Any)(implicit trace: Trace): IO[IOException, Unit]
/** Print a line to stdout */
def printLine(line: => Any)(implicit trace: Trace): IO[IOException, Unit]
/** Print a value to stderr */
def printError(line: => Any)(implicit trace: Trace): IO[IOException, Unit]
/** Print a line to stderr */
def printLineError(line: => Any)(implicit trace: Trace): IO[IOException, Unit]
/** Read a line from stdin */
def readLine(implicit trace: Trace): IO[IOException, String]
/** Read a line from stdin with a prompt */
def readLine(prompt: String)(implicit trace: Trace): IO[IOException, String]
}
// Static access methods
object Console {
/** Print to stdout */
def print(line: => Any): IO[IOException, Unit]
/** Print line to stdout */
def printLine(line: => Any): IO[IOException, Unit]
/** Print to stderr */
def printError(line: => Any): IO[IOException, Unit]
/** Print line to stderr */
def printLineError(line: => Any): IO[IOException, Unit]
/** Read from stdin */
def readLine: IO[IOException, String]
/** Read from stdin with prompt */
def readLine(prompt: String): IO[IOException, String]
}Usage Examples:
import zio._
// Basic console I/O
val interactive = for {
_ <- Console.printLine("What's your name?")
name <- Console.readLine
_ <- Console.printLine(s"Hello, $name!")
} yield ()
// Error handling with console
val safeConsole = for {
result <- Console.readLine("Enter a number: ")
.flatMap(input => ZIO.attempt(input.toInt))
.catchAll(error =>
Console.printLineError(s"Invalid input: $error") *>
ZIO.succeed(-1)
)
_ <- Console.printLine(s"You entered: $result")
} yield ()
// Formatted output
val reportGeneration = for {
users <- fetchUsers()
_ <- Console.printLine("=== User Report ===")
_ <- ZIO.foreach(users) { user =>
Console.printLine(f"${user.id}%5d | ${user.name}%-20s | ${user.email}")
}
_ <- Console.printLine(s"Total users: ${users.length}")
} yield ()
// Interactive menu system
def showMenu(): IO[IOException, String] = {
for {
_ <- Console.printLine("\n=== Main Menu ===")
_ <- Console.printLine("1. List users")
_ <- Console.printLine("2. Add user")
_ <- Console.printLine("3. Delete user")
_ <- Console.printLine("4. Exit")
choice <- Console.readLine("Select option: ")
} yield choice
}Service for time-related operations including current time, scheduling, and delays.
/**
* Service for time-based operations
*/
trait Clock {
/** Get current time in specified units */
def currentTime(unit: => TimeUnit): UIO[Long]
/** Get current time in specified chrono units */
def currentTime(unit: => ChronoUnit): UIO[Long]
/** Get current date and time */
def currentDateTime: UIO[OffsetDateTime]
/** Get current instant */
def instant: UIO[Instant]
/** Get current local date and time */
def localDateTime: UIO[LocalDateTime]
/** Get system nano time (for measuring durations) */
def nanoTime: UIO[Long]
/** Sleep for the specified duration */
def sleep(duration: => Duration): UIO[Unit]
/** Get the scheduler used by this clock */
def scheduler: UIO[Scheduler]
/** Get the underlying Java clock */
def javaClock: UIO[java.time.Clock]
}
// Static access methods
object Clock {
/** Get current time in units */
def currentTime(unit: => TimeUnit): UIO[Long]
/** Get system nano time */
def nanoTime: UIO[Long]
/** Sleep for duration */
def sleep(duration: => Duration): UIO[Unit]
/** Get current date/time */
def currentDateTime: UIO[OffsetDateTime]
/** Get current instant */
def instant: UIO[Instant]
}Usage Examples:
import zio._
import java.time._
import java.util.concurrent.TimeUnit
// Basic timing operations
val timingExample = for {
start <- Clock.nanoTime
_ <- heavyComputation
end <- Clock.nanoTime
duration = (end - start) / 1_000_000 // Convert to milliseconds
_ <- Console.printLine(s"Computation took ${duration}ms")
} yield ()
// Scheduled operations
val scheduler = for {
_ <- Console.printLine("Starting periodic task...")
_ <- (for {
now <- Clock.currentDateTime
_ <- Console.printLine(s"Heartbeat at $now")
} yield ()).repeat(Schedule.fixed(10.seconds))
} yield ()
// Timeout with custom timing
val withTimeout = for {
start <- Clock.currentTime(TimeUnit.MILLISECONDS)
result <- longRunningTask.timeout(30.seconds)
end <- Clock.currentTime(TimeUnit.MILLISECONDS)
_ <- Console.printLine(s"Operation completed in ${end - start}ms")
} yield result
// Date/time formatting and processing
val dateProcessing = for {
now <- Clock.currentDateTime
formatted = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
_ <- Console.printLine(s"Current time: $formatted")
// Check if it's business hours
hour <- Clock.localDateTime.map(_.getHour)
isBusiness = hour >= 9 && hour < 17
_ <- Console.printLine(s"Business hours: $isBusiness")
} yield ()
// Performance measurement
def measurePerformance[A](operation: UIO[A]): UIO[(A, Duration)] = {
for {
start <- Clock.nanoTime
result <- operation
end <- Clock.nanoTime
duration = Duration.fromNanos(end - start)
} yield (result, duration)
}
val performanceTest = for {
(result, duration) <- measurePerformance(computeExpensiveValue())
_ <- Console.printLine(s"Result: $result, Time: $duration")
} yield ()Service for generating various types of random values with seedable and repeatable behavior.
/**
* Service for random value generation
*/
trait Random {
/** Generate random boolean */
def nextBoolean: UIO[Boolean]
/** Generate random int */
def nextInt: UIO[Int]
/** Generate random long */
def nextLong: UIO[Long]
/** Generate random float [0.0, 1.0) */
def nextFloat: UIO[Float]
/** Generate random double [0.0, 1.0) */
def nextDouble: UIO[Double]
/** Generate random gaussian (normal distribution) */
def nextGaussian: UIO[Double]
/** Generate random int in range [0, n) */
def nextIntBounded(n: => Int): UIO[Int]
/** Generate random int in range [min, max) */
def nextIntBetween(min: => Int, max: => Int): UIO[Int]
/** Generate random long in range [0, n) */
def nextLongBounded(n: => Long): UIO[Long]
/** Generate random long in range [min, max) */
def nextLongBetween(min: => Long, max: => Long): UIO[Long]
/** Generate random double in range [min, max) */
def nextDoubleBetween(min: => Double, max: => Double): UIO[Double]
/** Generate random float in range [min, max) */
def nextFloatBetween(min: => Float, max: => Float): UIO[Float]
/** Generate array of random bytes */
def nextBytes(length: => Int): UIO[Chunk[Byte]]
/** Generate random string of specified length */
def nextString(length: => Int): UIO[String]
/** Generate random printable character */
def nextPrintableChar: UIO[Char]
/** Generate random UUID */
def nextUUID: UIO[UUID]
/** Shuffle a collection randomly */
def shuffle[A](collection: => Collection[A]): UIO[Collection[A]]
/** Set the random seed */
def setSeed(seed: => Long): UIO[Unit]
}
// Static access methods
object Random {
def nextInt: UIO[Int]
def nextBoolean: UIO[Boolean]
def nextDouble: UIO[Double]
def nextIntBounded(n: => Int): UIO[Int]
def nextBytes(length: => Int): UIO[Chunk[Byte]]
def nextUUID: UIO[UUID]
def shuffle[A](list: => List[A]): UIO[List[A]]
}Usage Examples:
import zio._
import java.util.UUID
// Basic random generation
val randomExample = for {
randomInt <- Random.nextInt
randomDouble <- Random.nextDouble
randomBool <- Random.nextBoolean
_ <- Console.printLine(s"Int: $randomInt, Double: $randomDouble, Bool: $randomBool")
} yield ()
// Game mechanics with random
val diceRoll = for {
die1 <- Random.nextIntBounded(6).map(_ + 1)
die2 <- Random.nextIntBounded(6).map(_ + 1)
total = die1 + die2
_ <- Console.printLine(s"Rolled: $die1 + $die2 = $total")
} yield total
// Random selection and shuffling
val randomSelection = for {
options <- ZIO.succeed(List("Option A", "Option B", "Option C", "Option D"))
shuffled <- Random.shuffle(options)
selected <- shuffled.headOption match {
case Some(choice) => ZIO.succeed(choice)
case None => ZIO.fail("No options available")
}
_ <- Console.printLine(s"Selected: $selected")
} yield selected
// Generate test data
def generateTestUser(): UIO[TestUser] = {
for {
id <- Random.nextUUID
age <- Random.nextIntBetween(18, 80)
salary <- Random.nextDoubleBetween(30000.0, 150000.0)
active <- Random.nextBoolean
nameLen <- Random.nextIntBetween(5, 15)
name <- Random.nextString(nameLen)
} yield TestUser(id, name, age, salary, active)
}
// Simulation with seeded random
val reproducibleSimulation = for {
_ <- Random.setSeed(12345) // Set seed for reproducibility
values <- ZIO.foreach(1 to 10)(_ => Random.nextGaussian)
mean = values.sum / values.length
_ <- Console.printLine(s"Mean of gaussian values: $mean")
} yield mean
// Random delay for chaos engineering
val chaosDelay = for {
delayMs <- Random.nextIntBetween(100, 5000)
_ <- Console.printLine(s"Adding chaos delay: ${delayMs}ms")
_ <- Clock.sleep(delayMs.millis)
} yield ()
// Random sampling
def sampleFromList[A](list: List[A], sampleSize: Int): UIO[List[A]] = {
for {
shuffled <- Random.shuffle(list)
} yield shuffled.take(sampleSize)
}
val samplingExample = for {
allUsers <- fetchAllUsers()
sampleUsers <- sampleFromList(allUsers, 10)
_ <- Console.printLine(s"Sampled ${sampleUsers.length} users for testing")
} yield sampleUsersService for accessing system environment variables, properties, and system information.
/**
* Service for system environment and properties access
*/
trait System {
/** Get environment variable */
def env(variable: => String): IO[SecurityException, Option[String]]
/** Get environment variable with fallback */
def envOrElse(variable: => String, alt: => String): IO[SecurityException, String]
/** Get environment variable with optional fallback */
def envOrOption(variable: => String, alt: => Option[String]): IO[SecurityException, Option[String]]
/** Get all environment variables */
def envs: IO[SecurityException, Map[String, String]]
/** Get system property */
def property(prop: => String): IO[Throwable, Option[String]]
/** Get system property with fallback */
def propertyOrElse(prop: => String, alt: => String): IO[Throwable, String]
/** Get system property with optional fallback */
def propertyOrOption(prop: => String, alt: => Option[String]): IO[Throwable, Option[String]]
/** Get all system properties */
def properties: IO[Throwable, Map[String, String]]
/** Get system line separator */
def lineSeparator: UIO[String]
}
// Static access methods
object System {
def env(variable: => String): IO[SecurityException, Option[String]]
def envs: IO[SecurityException, Map[String, String]]
def property(prop: => String): Task[Option[String]]
def properties: Task[Map[String, String]]
def lineSeparator: UIO[String]
/** Current operating system */
lazy val os: OS
sealed trait OS {
def isWindows: Boolean
def isMac: Boolean
def isUnix: Boolean
def isSolaris: Boolean
def isUnknown: Boolean
}
}Usage Examples:
import zio._
// Configuration from environment
val loadConfig = for {
port <- System.env("SERVER_PORT").map(_.getOrElse("8080"))
host <- System.env("SERVER_HOST").map(_.getOrElse("localhost"))
dbUrl <- System.env("DATABASE_URL").someOrElse(
ZIO.fail("DATABASE_URL environment variable is required")
)
debug <- System.env("DEBUG").map(_.contains("true"))
_ <- Console.printLine(s"Server config: $host:$port, DB: $dbUrl, Debug: $debug")
} yield AppConfig(host, port.toInt, dbUrl, debug)
// System information gathering
val systemInfo = for {
javaVersion <- System.property("java.version").map(_.getOrElse("unknown"))
osName <- System.property("os.name").map(_.getOrElse("unknown"))
osVersion <- System.property("os.version").map(_.getOrElse("unknown"))
userHome <- System.property("user.home").map(_.getOrElse("unknown"))
tmpDir <- System.property("java.io.tmpdir").map(_.getOrElse("/tmp"))
lineSep <- System.lineSeparator
_ <- Console.printLine("=== System Information ===")
_ <- Console.printLine(s"Java Version: $javaVersion")
_ <- Console.printLine(s"OS: $osName $osVersion")
_ <- Console.printLine(s"User Home: $userHome")
_ <- Console.printLine(s"Temp Dir: $tmpDir")
_ <- Console.printLine(s"Line Separator: ${lineSep.replace("\n", "\\n").replace("\r", "\\r")}")
} yield ()
// Platform-specific behavior
val platformSpecific = for {
_ <- if (System.os.isWindows) {
Console.printLine("Running Windows-specific code")
} else if (System.os.isUnix) {
Console.printLine("Running Unix-specific code")
} else {
Console.printLine("Running generic code")
}
pathSep <- System.property("path.separator").map(_.getOrElse(":"))
_ <- Console.printLine(s"Path separator: $pathSep")
} yield ()
// Environment variable validation
val validateEnvironment = for {
requiredVars <- ZIO.succeed(List("DATABASE_URL", "API_KEY", "LOG_LEVEL"))
missing <- ZIO.foldLeft(requiredVars)(List.empty[String]) { (acc, varName) =>
System.env(varName).map {
case Some(_) => acc
case None => varName :: acc
}
}
_ <- ZIO.when(missing.nonEmpty) {
Console.printLineError(s"Missing required environment variables: ${missing.mkString(", ")}") *>
ZIO.fail("Environment validation failed")
}
_ <- Console.printLine("Environment validation passed")
} yield ()
// Dynamic property loading
def loadPropertiesFromFile(filename: String): Task[Map[String, String]] = {
for {
userHome <- System.property("user.home").someOrFail(new RuntimeException("user.home not set"))
configPath = s"$userHome/$filename"
_ <- Console.printLine(s"Loading properties from: $configPath")
// Implementation would read from file
properties <- ZIO.succeed(Map("app.name" -> "MyApp", "app.version" -> "1.0"))
} yield properties
}
// Environment-based feature flags
val featureFlags = for {
allEnvVars <- System.envs
flags = allEnvVars.collect {
case (key, value) if key.startsWith("FEATURE_") =>
key.stripPrefix("FEATURE_") -> value.toBoolean
}
_ <- Console.printLine(s"Feature flags: $flags")
} yield flagsPatterns for testing services and providing mock implementations.
// Mock console for testing
class TestConsole(
inputs: Ref[List[String]],
outputs: Ref[List[String]],
errors: Ref[List[String]]
) extends Console {
def print(line: => Any): IO[IOException, Unit] =
outputs.update(_ :+ line.toString).unit
def printLine(line: => Any): IO[IOException, Unit] =
outputs.update(_ :+ (line.toString + "\n")).unit
def printError(line: => Any): IO[IOException, Unit] =
errors.update(_ :+ line.toString).unit
def printLineError(line: => Any): IO[IOException, Unit] =
errors.update(_ :+ (line.toString + "\n")).unit
def readLine: IO[IOException, String] =
inputs.modify {
case head :: tail => (head, tail)
case Nil => throw new IOException("No more input available")
}
def readLine(prompt: String): IO[IOException, String] =
print(prompt) *> readLine
}
// Test clock for deterministic timing
class TestClock(timeRef: Ref[Long]) extends Clock {
def currentTime(unit: TimeUnit): UIO[Long] =
timeRef.get.map(unit.convert(_, TimeUnit.MILLISECONDS))
def nanoTime: UIO[Long] =
timeRef.get.map(_ * 1_000_000)
def sleep(duration: Duration): UIO[Unit] =
timeRef.update(_ + duration.toMillis).unit
def currentDateTime: UIO[OffsetDateTime] =
timeRef.get.map(millis =>
OffsetDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneOffset.UTC)
)
// ... other methods
}
// Deterministic random for testing
class TestRandom(seedRef: Ref[Long]) extends Random {
def nextInt: UIO[Int] =
seedRef.updateAndGet(seed => (seed * 1103515245L + 12345L) & 0x7fffffffL).map(_.toInt)
def nextBoolean: UIO[Boolean] =
nextInt.map(_ % 2 == 0)
// ... other methods
}Usage Examples:
// Service testing example
val testProgram = for {
inputs <- Ref.make(List("Alice", "30"))
outputs <- Ref.make(List.empty[String])
errors <- Ref.make(List.empty[String])
testConsole = new TestConsole(inputs, outputs, errors)
// Run program with test console
_ <- (for {
name <- Console.readLine("Name: ")
age <- Console.readLine("Age: ")
_ <- Console.printLine(s"Hello $name, you are $age years old")
} yield ()).provideService(testConsole)
// Verify outputs
allOutputs <- outputs.get
_ <- Console.printLine(s"Captured outputs: $allOutputs")
} yield ()