0
# Built-in Services
1
2
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.
3
4
## Capabilities
5
6
### Console Service
7
8
Service for console input/output operations with proper error handling and encoding support.
9
10
```scala { .api }
11
/**
12
* Service for console I/O operations
13
*/
14
trait Console {
15
/** Print a value to stdout */
16
def print(line: => Any)(implicit trace: Trace): IO[IOException, Unit]
17
18
/** Print a line to stdout */
19
def printLine(line: => Any)(implicit trace: Trace): IO[IOException, Unit]
20
21
/** Print a value to stderr */
22
def printError(line: => Any)(implicit trace: Trace): IO[IOException, Unit]
23
24
/** Print a line to stderr */
25
def printLineError(line: => Any)(implicit trace: Trace): IO[IOException, Unit]
26
27
/** Read a line from stdin */
28
def readLine(implicit trace: Trace): IO[IOException, String]
29
30
/** Read a line from stdin with a prompt */
31
def readLine(prompt: String)(implicit trace: Trace): IO[IOException, String]
32
}
33
34
// Static access methods
35
object Console {
36
/** Print to stdout */
37
def print(line: => Any): IO[IOException, Unit]
38
39
/** Print line to stdout */
40
def printLine(line: => Any): IO[IOException, Unit]
41
42
/** Print to stderr */
43
def printError(line: => Any): IO[IOException, Unit]
44
45
/** Print line to stderr */
46
def printLineError(line: => Any): IO[IOException, Unit]
47
48
/** Read from stdin */
49
def readLine: IO[IOException, String]
50
51
/** Read from stdin with prompt */
52
def readLine(prompt: String): IO[IOException, String]
53
}
54
```
55
56
**Usage Examples:**
57
58
```scala
59
import zio._
60
61
// Basic console I/O
62
val interactive = for {
63
_ <- Console.printLine("What's your name?")
64
name <- Console.readLine
65
_ <- Console.printLine(s"Hello, $name!")
66
} yield ()
67
68
// Error handling with console
69
val safeConsole = for {
70
result <- Console.readLine("Enter a number: ")
71
.flatMap(input => ZIO.attempt(input.toInt))
72
.catchAll(error =>
73
Console.printLineError(s"Invalid input: $error") *>
74
ZIO.succeed(-1)
75
)
76
_ <- Console.printLine(s"You entered: $result")
77
} yield ()
78
79
// Formatted output
80
val reportGeneration = for {
81
users <- fetchUsers()
82
_ <- Console.printLine("=== User Report ===")
83
_ <- ZIO.foreach(users) { user =>
84
Console.printLine(f"${user.id}%5d | ${user.name}%-20s | ${user.email}")
85
}
86
_ <- Console.printLine(s"Total users: ${users.length}")
87
} yield ()
88
89
// Interactive menu system
90
def showMenu(): IO[IOException, String] = {
91
for {
92
_ <- Console.printLine("\n=== Main Menu ===")
93
_ <- Console.printLine("1. List users")
94
_ <- Console.printLine("2. Add user")
95
_ <- Console.printLine("3. Delete user")
96
_ <- Console.printLine("4. Exit")
97
choice <- Console.readLine("Select option: ")
98
} yield choice
99
}
100
```
101
102
### Clock Service
103
104
Service for time-related operations including current time, scheduling, and delays.
105
106
```scala { .api }
107
/**
108
* Service for time-based operations
109
*/
110
trait Clock {
111
/** Get current time in specified units */
112
def currentTime(unit: => TimeUnit): UIO[Long]
113
114
/** Get current time in specified chrono units */
115
def currentTime(unit: => ChronoUnit): UIO[Long]
116
117
/** Get current date and time */
118
def currentDateTime: UIO[OffsetDateTime]
119
120
/** Get current instant */
121
def instant: UIO[Instant]
122
123
/** Get current local date and time */
124
def localDateTime: UIO[LocalDateTime]
125
126
/** Get system nano time (for measuring durations) */
127
def nanoTime: UIO[Long]
128
129
/** Sleep for the specified duration */
130
def sleep(duration: => Duration): UIO[Unit]
131
132
/** Get the scheduler used by this clock */
133
def scheduler: UIO[Scheduler]
134
135
/** Get the underlying Java clock */
136
def javaClock: UIO[java.time.Clock]
137
}
138
139
// Static access methods
140
object Clock {
141
/** Get current time in units */
142
def currentTime(unit: => TimeUnit): UIO[Long]
143
144
/** Get system nano time */
145
def nanoTime: UIO[Long]
146
147
/** Sleep for duration */
148
def sleep(duration: => Duration): UIO[Unit]
149
150
/** Get current date/time */
151
def currentDateTime: UIO[OffsetDateTime]
152
153
/** Get current instant */
154
def instant: UIO[Instant]
155
}
156
```
157
158
**Usage Examples:**
159
160
```scala
161
import zio._
162
import java.time._
163
import java.util.concurrent.TimeUnit
164
165
// Basic timing operations
166
val timingExample = for {
167
start <- Clock.nanoTime
168
_ <- heavyComputation
169
end <- Clock.nanoTime
170
duration = (end - start) / 1_000_000 // Convert to milliseconds
171
_ <- Console.printLine(s"Computation took ${duration}ms")
172
} yield ()
173
174
// Scheduled operations
175
val scheduler = for {
176
_ <- Console.printLine("Starting periodic task...")
177
_ <- (for {
178
now <- Clock.currentDateTime
179
_ <- Console.printLine(s"Heartbeat at $now")
180
} yield ()).repeat(Schedule.fixed(10.seconds))
181
} yield ()
182
183
// Timeout with custom timing
184
val withTimeout = for {
185
start <- Clock.currentTime(TimeUnit.MILLISECONDS)
186
result <- longRunningTask.timeout(30.seconds)
187
end <- Clock.currentTime(TimeUnit.MILLISECONDS)
188
_ <- Console.printLine(s"Operation completed in ${end - start}ms")
189
} yield result
190
191
// Date/time formatting and processing
192
val dateProcessing = for {
193
now <- Clock.currentDateTime
194
formatted = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
195
_ <- Console.printLine(s"Current time: $formatted")
196
197
// Check if it's business hours
198
hour <- Clock.localDateTime.map(_.getHour)
199
isBusiness = hour >= 9 && hour < 17
200
_ <- Console.printLine(s"Business hours: $isBusiness")
201
} yield ()
202
203
// Performance measurement
204
def measurePerformance[A](operation: UIO[A]): UIO[(A, Duration)] = {
205
for {
206
start <- Clock.nanoTime
207
result <- operation
208
end <- Clock.nanoTime
209
duration = Duration.fromNanos(end - start)
210
} yield (result, duration)
211
}
212
213
val performanceTest = for {
214
(result, duration) <- measurePerformance(computeExpensiveValue())
215
_ <- Console.printLine(s"Result: $result, Time: $duration")
216
} yield ()
217
```
218
219
### Random Service
220
221
Service for generating various types of random values with seedable and repeatable behavior.
222
223
```scala { .api }
224
/**
225
* Service for random value generation
226
*/
227
trait Random {
228
/** Generate random boolean */
229
def nextBoolean: UIO[Boolean]
230
231
/** Generate random int */
232
def nextInt: UIO[Int]
233
234
/** Generate random long */
235
def nextLong: UIO[Long]
236
237
/** Generate random float [0.0, 1.0) */
238
def nextFloat: UIO[Float]
239
240
/** Generate random double [0.0, 1.0) */
241
def nextDouble: UIO[Double]
242
243
/** Generate random gaussian (normal distribution) */
244
def nextGaussian: UIO[Double]
245
246
/** Generate random int in range [0, n) */
247
def nextIntBounded(n: => Int): UIO[Int]
248
249
/** Generate random int in range [min, max) */
250
def nextIntBetween(min: => Int, max: => Int): UIO[Int]
251
252
/** Generate random long in range [0, n) */
253
def nextLongBounded(n: => Long): UIO[Long]
254
255
/** Generate random long in range [min, max) */
256
def nextLongBetween(min: => Long, max: => Long): UIO[Long]
257
258
/** Generate random double in range [min, max) */
259
def nextDoubleBetween(min: => Double, max: => Double): UIO[Double]
260
261
/** Generate random float in range [min, max) */
262
def nextFloatBetween(min: => Float, max: => Float): UIO[Float]
263
264
/** Generate array of random bytes */
265
def nextBytes(length: => Int): UIO[Chunk[Byte]]
266
267
/** Generate random string of specified length */
268
def nextString(length: => Int): UIO[String]
269
270
/** Generate random printable character */
271
def nextPrintableChar: UIO[Char]
272
273
/** Generate random UUID */
274
def nextUUID: UIO[UUID]
275
276
/** Shuffle a collection randomly */
277
def shuffle[A](collection: => Collection[A]): UIO[Collection[A]]
278
279
/** Set the random seed */
280
def setSeed(seed: => Long): UIO[Unit]
281
}
282
283
// Static access methods
284
object Random {
285
def nextInt: UIO[Int]
286
def nextBoolean: UIO[Boolean]
287
def nextDouble: UIO[Double]
288
def nextIntBounded(n: => Int): UIO[Int]
289
def nextBytes(length: => Int): UIO[Chunk[Byte]]
290
def nextUUID: UIO[UUID]
291
def shuffle[A](list: => List[A]): UIO[List[A]]
292
}
293
```
294
295
**Usage Examples:**
296
297
```scala
298
import zio._
299
import java.util.UUID
300
301
// Basic random generation
302
val randomExample = for {
303
randomInt <- Random.nextInt
304
randomDouble <- Random.nextDouble
305
randomBool <- Random.nextBoolean
306
_ <- Console.printLine(s"Int: $randomInt, Double: $randomDouble, Bool: $randomBool")
307
} yield ()
308
309
// Game mechanics with random
310
val diceRoll = for {
311
die1 <- Random.nextIntBounded(6).map(_ + 1)
312
die2 <- Random.nextIntBounded(6).map(_ + 1)
313
total = die1 + die2
314
_ <- Console.printLine(s"Rolled: $die1 + $die2 = $total")
315
} yield total
316
317
// Random selection and shuffling
318
val randomSelection = for {
319
options <- ZIO.succeed(List("Option A", "Option B", "Option C", "Option D"))
320
shuffled <- Random.shuffle(options)
321
selected <- shuffled.headOption match {
322
case Some(choice) => ZIO.succeed(choice)
323
case None => ZIO.fail("No options available")
324
}
325
_ <- Console.printLine(s"Selected: $selected")
326
} yield selected
327
328
// Generate test data
329
def generateTestUser(): UIO[TestUser] = {
330
for {
331
id <- Random.nextUUID
332
age <- Random.nextIntBetween(18, 80)
333
salary <- Random.nextDoubleBetween(30000.0, 150000.0)
334
active <- Random.nextBoolean
335
nameLen <- Random.nextIntBetween(5, 15)
336
name <- Random.nextString(nameLen)
337
} yield TestUser(id, name, age, salary, active)
338
}
339
340
// Simulation with seeded random
341
val reproducibleSimulation = for {
342
_ <- Random.setSeed(12345) // Set seed for reproducibility
343
values <- ZIO.foreach(1 to 10)(_ => Random.nextGaussian)
344
mean = values.sum / values.length
345
_ <- Console.printLine(s"Mean of gaussian values: $mean")
346
} yield mean
347
348
// Random delay for chaos engineering
349
val chaosDelay = for {
350
delayMs <- Random.nextIntBetween(100, 5000)
351
_ <- Console.printLine(s"Adding chaos delay: ${delayMs}ms")
352
_ <- Clock.sleep(delayMs.millis)
353
} yield ()
354
355
// Random sampling
356
def sampleFromList[A](list: List[A], sampleSize: Int): UIO[List[A]] = {
357
for {
358
shuffled <- Random.shuffle(list)
359
} yield shuffled.take(sampleSize)
360
}
361
362
val samplingExample = for {
363
allUsers <- fetchAllUsers()
364
sampleUsers <- sampleFromList(allUsers, 10)
365
_ <- Console.printLine(s"Sampled ${sampleUsers.length} users for testing")
366
} yield sampleUsers
367
```
368
369
### System Service
370
371
Service for accessing system environment variables, properties, and system information.
372
373
```scala { .api }
374
/**
375
* Service for system environment and properties access
376
*/
377
trait System {
378
/** Get environment variable */
379
def env(variable: => String): IO[SecurityException, Option[String]]
380
381
/** Get environment variable with fallback */
382
def envOrElse(variable: => String, alt: => String): IO[SecurityException, String]
383
384
/** Get environment variable with optional fallback */
385
def envOrOption(variable: => String, alt: => Option[String]): IO[SecurityException, Option[String]]
386
387
/** Get all environment variables */
388
def envs: IO[SecurityException, Map[String, String]]
389
390
/** Get system property */
391
def property(prop: => String): IO[Throwable, Option[String]]
392
393
/** Get system property with fallback */
394
def propertyOrElse(prop: => String, alt: => String): IO[Throwable, String]
395
396
/** Get system property with optional fallback */
397
def propertyOrOption(prop: => String, alt: => Option[String]): IO[Throwable, Option[String]]
398
399
/** Get all system properties */
400
def properties: IO[Throwable, Map[String, String]]
401
402
/** Get system line separator */
403
def lineSeparator: UIO[String]
404
}
405
406
// Static access methods
407
object System {
408
def env(variable: => String): IO[SecurityException, Option[String]]
409
def envs: IO[SecurityException, Map[String, String]]
410
def property(prop: => String): Task[Option[String]]
411
def properties: Task[Map[String, String]]
412
def lineSeparator: UIO[String]
413
414
/** Current operating system */
415
lazy val os: OS
416
417
sealed trait OS {
418
def isWindows: Boolean
419
def isMac: Boolean
420
def isUnix: Boolean
421
def isSolaris: Boolean
422
def isUnknown: Boolean
423
}
424
}
425
```
426
427
**Usage Examples:**
428
429
```scala
430
import zio._
431
432
// Configuration from environment
433
val loadConfig = for {
434
port <- System.env("SERVER_PORT").map(_.getOrElse("8080"))
435
host <- System.env("SERVER_HOST").map(_.getOrElse("localhost"))
436
dbUrl <- System.env("DATABASE_URL").someOrElse(
437
ZIO.fail("DATABASE_URL environment variable is required")
438
)
439
debug <- System.env("DEBUG").map(_.contains("true"))
440
_ <- Console.printLine(s"Server config: $host:$port, DB: $dbUrl, Debug: $debug")
441
} yield AppConfig(host, port.toInt, dbUrl, debug)
442
443
// System information gathering
444
val systemInfo = for {
445
javaVersion <- System.property("java.version").map(_.getOrElse("unknown"))
446
osName <- System.property("os.name").map(_.getOrElse("unknown"))
447
osVersion <- System.property("os.version").map(_.getOrElse("unknown"))
448
userHome <- System.property("user.home").map(_.getOrElse("unknown"))
449
tmpDir <- System.property("java.io.tmpdir").map(_.getOrElse("/tmp"))
450
lineSep <- System.lineSeparator
451
452
_ <- Console.printLine("=== System Information ===")
453
_ <- Console.printLine(s"Java Version: $javaVersion")
454
_ <- Console.printLine(s"OS: $osName $osVersion")
455
_ <- Console.printLine(s"User Home: $userHome")
456
_ <- Console.printLine(s"Temp Dir: $tmpDir")
457
_ <- Console.printLine(s"Line Separator: ${lineSep.replace("\n", "\\n").replace("\r", "\\r")}")
458
} yield ()
459
460
// Platform-specific behavior
461
val platformSpecific = for {
462
_ <- if (System.os.isWindows) {
463
Console.printLine("Running Windows-specific code")
464
} else if (System.os.isUnix) {
465
Console.printLine("Running Unix-specific code")
466
} else {
467
Console.printLine("Running generic code")
468
}
469
pathSep <- System.property("path.separator").map(_.getOrElse(":"))
470
_ <- Console.printLine(s"Path separator: $pathSep")
471
} yield ()
472
473
// Environment variable validation
474
val validateEnvironment = for {
475
requiredVars <- ZIO.succeed(List("DATABASE_URL", "API_KEY", "LOG_LEVEL"))
476
477
missing <- ZIO.foldLeft(requiredVars)(List.empty[String]) { (acc, varName) =>
478
System.env(varName).map {
479
case Some(_) => acc
480
case None => varName :: acc
481
}
482
}
483
484
_ <- ZIO.when(missing.nonEmpty) {
485
Console.printLineError(s"Missing required environment variables: ${missing.mkString(", ")}") *>
486
ZIO.fail("Environment validation failed")
487
}
488
489
_ <- Console.printLine("Environment validation passed")
490
} yield ()
491
492
// Dynamic property loading
493
def loadPropertiesFromFile(filename: String): Task[Map[String, String]] = {
494
for {
495
userHome <- System.property("user.home").someOrFail(new RuntimeException("user.home not set"))
496
configPath = s"$userHome/$filename"
497
_ <- Console.printLine(s"Loading properties from: $configPath")
498
// Implementation would read from file
499
properties <- ZIO.succeed(Map("app.name" -> "MyApp", "app.version" -> "1.0"))
500
} yield properties
501
}
502
503
// Environment-based feature flags
504
val featureFlags = for {
505
allEnvVars <- System.envs
506
flags = allEnvVars.collect {
507
case (key, value) if key.startsWith("FEATURE_") =>
508
key.stripPrefix("FEATURE_") -> value.toBoolean
509
}
510
_ <- Console.printLine(s"Feature flags: $flags")
511
} yield flags
512
```
513
514
### Service Testing and Mocking
515
516
Patterns for testing services and providing mock implementations.
517
518
```scala { .api }
519
// Mock console for testing
520
class TestConsole(
521
inputs: Ref[List[String]],
522
outputs: Ref[List[String]],
523
errors: Ref[List[String]]
524
) extends Console {
525
526
def print(line: => Any): IO[IOException, Unit] =
527
outputs.update(_ :+ line.toString).unit
528
529
def printLine(line: => Any): IO[IOException, Unit] =
530
outputs.update(_ :+ (line.toString + "\n")).unit
531
532
def printError(line: => Any): IO[IOException, Unit] =
533
errors.update(_ :+ line.toString).unit
534
535
def printLineError(line: => Any): IO[IOException, Unit] =
536
errors.update(_ :+ (line.toString + "\n")).unit
537
538
def readLine: IO[IOException, String] =
539
inputs.modify {
540
case head :: tail => (head, tail)
541
case Nil => throw new IOException("No more input available")
542
}
543
544
def readLine(prompt: String): IO[IOException, String] =
545
print(prompt) *> readLine
546
}
547
548
// Test clock for deterministic timing
549
class TestClock(timeRef: Ref[Long]) extends Clock {
550
def currentTime(unit: TimeUnit): UIO[Long] =
551
timeRef.get.map(unit.convert(_, TimeUnit.MILLISECONDS))
552
553
def nanoTime: UIO[Long] =
554
timeRef.get.map(_ * 1_000_000)
555
556
def sleep(duration: Duration): UIO[Unit] =
557
timeRef.update(_ + duration.toMillis).unit
558
559
def currentDateTime: UIO[OffsetDateTime] =
560
timeRef.get.map(millis =>
561
OffsetDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneOffset.UTC)
562
)
563
564
// ... other methods
565
}
566
567
// Deterministic random for testing
568
class TestRandom(seedRef: Ref[Long]) extends Random {
569
def nextInt: UIO[Int] =
570
seedRef.updateAndGet(seed => (seed * 1103515245L + 12345L) & 0x7fffffffL).map(_.toInt)
571
572
def nextBoolean: UIO[Boolean] =
573
nextInt.map(_ % 2 == 0)
574
575
// ... other methods
576
}
577
```
578
579
**Usage Examples:**
580
581
```scala
582
// Service testing example
583
val testProgram = for {
584
inputs <- Ref.make(List("Alice", "30"))
585
outputs <- Ref.make(List.empty[String])
586
errors <- Ref.make(List.empty[String])
587
588
testConsole = new TestConsole(inputs, outputs, errors)
589
590
// Run program with test console
591
_ <- (for {
592
name <- Console.readLine("Name: ")
593
age <- Console.readLine("Age: ")
594
_ <- Console.printLine(s"Hello $name, you are $age years old")
595
} yield ()).provideService(testConsole)
596
597
// Verify outputs
598
allOutputs <- outputs.get
599
_ <- Console.printLine(s"Captured outputs: $allOutputs")
600
} yield ()
601
```