0
# Application Framework
1
2
Cats Effect provides IOApp, a complete application framework for building Scala applications with proper lifecycle management, threading, and shutdown handling using the IO monad.
3
4
## Capabilities
5
6
### IOApp Trait
7
8
The main application trait that provides infrastructure for IO-based applications.
9
10
```scala { .api }
11
/**
12
* Main application trait for IO-based applications
13
*/
14
trait IOApp {
15
/** Main application logic - implement this method */
16
def run(args: List[String]): IO[ExitCode]
17
18
/** Implicit ContextShift for thread management */
19
implicit def contextShift: ContextShift[IO]
20
21
/** Implicit Timer for time-based operations */
22
implicit def timer: Timer[IO]
23
24
/** JVM main method - provided automatically */
25
final def main(args: Array[String]): Unit
26
27
/** Called on JVM shutdown - override for custom cleanup */
28
protected def onShutdown(): Unit = ()
29
30
/** Called on uncaught exceptions - override for custom handling */
31
protected def reportFailure(err: Throwable): IO[Unit] =
32
IO(err.printStackTrace())
33
}
34
```
35
36
**Usage Examples:**
37
38
```scala
39
import cats.effect._
40
import scala.concurrent.duration._
41
42
// Simple application
43
object HelloWorldApp extends IOApp {
44
def run(args: List[String]): IO[ExitCode] =
45
IO(println("Hello, World!")).as(ExitCode.Success)
46
}
47
48
// Application with arguments and error handling
49
object FileProcessorApp extends IOApp {
50
def run(args: List[String]): IO[ExitCode] =
51
args.headOption match {
52
case Some(filename) => processFile(filename).as(ExitCode.Success)
53
case None =>
54
IO(Console.err.println("Usage: FileProcessor <filename>"))
55
.as(ExitCode.Error)
56
}
57
58
private def processFile(filename: String): IO[Unit] =
59
for {
60
content <- IO(scala.io.Source.fromFile(filename).mkString)
61
lines = content.split('\n').length
62
_ <- IO(println(s"File $filename has $lines lines"))
63
} yield ()
64
}
65
```
66
67
### IOApp.Simple
68
69
Simplified version of IOApp for applications that don't need to handle command line arguments or return custom exit codes.
70
71
```scala { .api }
72
/**
73
* Simplified IOApp for applications without arguments or custom exit codes
74
*/
75
trait IOApp.Simple extends IOApp {
76
/** Simplified run method without arguments */
77
def run: IO[Unit]
78
79
/** Final implementation that calls run and returns Success */
80
final def run(args: List[String]): IO[ExitCode] =
81
run.as(ExitCode.Success)
82
}
83
```
84
85
**Usage Examples:**
86
87
```scala
88
// Simple server application
89
object WebServerApp extends IOApp.Simple {
90
def run: IO[Unit] =
91
for {
92
_ <- IO(println("Starting web server..."))
93
server <- createServer()
94
_ <- server.serve.compile.drain
95
} yield ()
96
}
97
98
// Background service
99
object BackgroundServiceApp extends IOApp.Simple {
100
def run: IO[Unit] =
101
for {
102
_ <- IO(println("Starting background service..."))
103
_ <- backgroundProcessor().foreverM
104
} yield ()
105
106
private def backgroundProcessor(): IO[Unit] =
107
for {
108
_ <- IO(println("Processing..."))
109
_ <- IO.sleep(10.seconds)
110
} yield ()
111
}
112
```
113
114
### ExitCode
115
116
Type-safe representation of application exit codes.
117
118
```scala { .api }
119
/**
120
* Type-safe exit code representation
121
*/
122
final case class ExitCode(code: Int) {
123
/** Check if this is a success code */
124
def isSuccess: Boolean = code == 0
125
126
/** Check if this is an error code */
127
def isError: Boolean = code != 0
128
}
129
130
object ExitCode {
131
/** Success exit code (0) */
132
val Success: ExitCode = ExitCode(0)
133
134
/** Generic error exit code (1) */
135
val Error: ExitCode = ExitCode(1)
136
137
/** Create from integer (clamped to valid range) */
138
def apply(code: Int): ExitCode = new ExitCode(code & 255)
139
140
/** Create from boolean (false = Success, true = Error) */
141
def fromBoolean(success: Boolean): ExitCode =
142
if (success) Success else Error
143
}
144
```
145
146
**Usage Examples:**
147
148
```scala
149
// Conditional exit codes
150
object ValidationApp extends IOApp {
151
def run(args: List[String]): IO[ExitCode] =
152
for {
153
isValid <- validateData()
154
exitCode = if (isValid) ExitCode.Success else ExitCode(2) // Custom error code
155
} yield exitCode
156
}
157
158
// Exit code from computation result
159
object ComputationApp extends IOApp {
160
def run(args: List[String]): IO[ExitCode] =
161
performComputation().attempt.map {
162
case Right(_) => ExitCode.Success
163
case Left(_) => ExitCode.Error
164
}
165
}
166
```
167
168
### SyncIO
169
170
Synchronous IO monad for operations that don't require async capabilities.
171
172
```scala { .api }
173
/**
174
* Synchronous IO monad without async/concurrency capabilities
175
*/
176
abstract class SyncIO[+A] {
177
/** Transform the result */
178
def map[B](f: A => B): SyncIO[B]
179
180
/** Chain another SyncIO computation */
181
def flatMap[B](f: A => SyncIO[B]): SyncIO[B]
182
183
/** Handle errors */
184
def handleErrorWith[AA >: A](f: Throwable => SyncIO[AA]): SyncIO[AA]
185
186
/** Convert errors to Either */
187
def attempt: SyncIO[Either[Throwable, A]]
188
189
/** Execute synchronously (safe) */
190
def unsafeRunSync(): A
191
192
/** Convert to regular IO */
193
def toIO: IO[A]
194
}
195
196
object SyncIO {
197
/** Lift a pure value */
198
def pure[A](a: A): SyncIO[A]
199
200
/** Suspend a side effect */
201
def delay[A](thunk: => A): SyncIO[A]
202
203
/** Suspend a SyncIO computation */
204
def defer[A](thunk: => SyncIO[A]): SyncIO[A]
205
206
/** Create a failed SyncIO */
207
def raiseError[A](e: Throwable): SyncIO[A]
208
209
/** Unit SyncIO */
210
val unit: SyncIO[Unit]
211
}
212
```
213
214
### Timer and Clock
215
216
Time-based operations and scheduling.
217
218
```scala { .api }
219
/**
220
* Timer for scheduling operations
221
*/
222
trait Timer[F[_]] {
223
/** Access to clock operations */
224
def clock: Clock[F]
225
226
/** Sleep for specified duration */
227
def sleep(duration: FiniteDuration): F[Unit]
228
}
229
230
/**
231
* Clock for accessing time information
232
*/
233
trait Clock[F[_]] {
234
/** Current wall clock time */
235
def realTime(unit: TimeUnit): F[Long]
236
237
/** Monotonic time (unaffected by system clock adjustments) */
238
def monotonic(unit: TimeUnit): F[Long]
239
240
/** Current wall clock time in milliseconds */
241
def realTimeMillis: F[Long] = realTime(MILLISECONDS)
242
243
/** Monotonic time in nanoseconds */
244
def monotonicNanos: F[Long] = monotonic(NANOSECONDS)
245
}
246
247
object Timer {
248
/** Get implicit Timer instance */
249
def apply[F[_]](implicit timer: Timer[F]): Timer[F] = timer
250
251
/** Derive Timer from Clock and ContextShift */
252
def derive[F[_]: Clock: ContextShift: Sync]: Timer[F]
253
}
254
```
255
256
**Usage Examples:**
257
258
```scala
259
// Scheduled operations
260
object ScheduledApp extends IOApp {
261
def run(args: List[String]): IO[ExitCode] =
262
for {
263
start <- Timer[IO].clock.monotonic(MILLISECONDS)
264
_ <- performWork()
265
end <- Timer[IO].clock.monotonic(MILLISECONDS)
266
_ <- IO(println(s"Work completed in ${end - start} ms"))
267
} yield ExitCode.Success
268
269
private def performWork(): IO[Unit] =
270
IO(println("Working...")) *> Timer[IO].sleep(2.seconds)
271
}
272
273
// Timeout handling
274
object TimeoutApp extends IOApp {
275
def run(args: List[String]): IO[ExitCode] =
276
longRunningOperation()
277
.timeout(30.seconds)
278
.handleErrorWith {
279
case _: TimeoutException =>
280
IO(println("Operation timed out")).as(ExitCode.Error)
281
case error =>
282
IO(println(s"Operation failed: ${error.getMessage}")).as(ExitCode.Error)
283
}
284
.as(ExitCode.Success)
285
}
286
```
287
288
### ContextShift
289
290
Thread management and execution context control.
291
292
```scala { .api }
293
/**
294
* Context shifting for thread management
295
*/
296
trait ContextShift[F[_]] {
297
/** Introduce an async boundary */
298
def shift: F[Unit]
299
300
/** Execute on specific ExecutionContext */
301
def evalOn[A](ec: ExecutionContext)(fa: F[A]): F[A]
302
}
303
304
object ContextShift {
305
/** Get implicit ContextShift instance */
306
def apply[F[_]](implicit cs: ContextShift[F]): ContextShift[F] = cs
307
308
/** Create from ExecutionContext */
309
def fromExecutionContext[F[_]: Sync](ec: ExecutionContext): ContextShift[F]
310
311
/** Create global ContextShift for IO */
312
def global: ContextShift[IO]
313
}
314
```
315
316
## Types
317
318
```scala { .api }
319
/**
320
* Main application trait
321
*/
322
trait IOApp {
323
def run(args: List[String]): IO[ExitCode]
324
implicit def contextShift: ContextShift[IO]
325
implicit def timer: Timer[IO]
326
}
327
328
/**
329
* Simplified application trait
330
*/
331
trait IOApp.Simple extends IOApp {
332
def run: IO[Unit]
333
}
334
335
/**
336
* Type-safe exit code
337
*/
338
final case class ExitCode(code: Int)
339
340
/**
341
* Synchronous IO monad
342
*/
343
abstract class SyncIO[+A]
344
345
/**
346
* Timer for scheduling operations
347
*/
348
trait Timer[F[_]] {
349
def clock: Clock[F]
350
def sleep(duration: FiniteDuration): F[Unit]
351
}
352
353
/**
354
* Clock for time access
355
*/
356
trait Clock[F[_]] {
357
def realTime(unit: TimeUnit): F[Long]
358
def monotonic(unit: TimeUnit): F[Long]
359
}
360
361
/**
362
* Context shifting for thread management
363
*/
364
trait ContextShift[F[_]] {
365
def shift: F[Unit]
366
def evalOn[A](ec: ExecutionContext)(fa: F[A]): F[A]
367
}
368
```