Introduction
Life was easy until now. Our whole program runs in memory, and we don’t have to worry about unreliable network connections, timeouts, or other external factors. But as we start to integrate with the outside world, we need to consider these aspects and how they affect our application.
In this part, we’ll introduce Cats Effect library that helps us deal with side effects and asynchronous programming in a functional way.
Dependencies
First, let’s add Cats Effect to our dependencies. Update project/Dependencies.scala
:
import sbt.*
object Dependencies {
private object Version {
val catsEffect = "3.6.3"
val iron = "3.0.0"
val munit = "1.1.1"
val munitScalaCheck = "1.1.0"
}
lazy val cats: ModuleID = "org.typelevel" %% "cats-effect" % Version.catsEffect
lazy val iron: ModuleID = "io.github.iltotore" %% "iron" % Version.iron
lazy val test: Seq[ModuleID] = {
Seq("org.scalameta" %% "munit" % Version.munit, "org.scalameta" %% "munit-scalacheck" % Version.munitScalaCheck)
.map(_ % "test")
}
}
And update build.sbt
:
[...]
lazy val dependencies = {
Dependencies.test :+
Dependencies.cats :+
Dependencies.iron
}
[...]
Cats Effect
Cats Effect is a runtime layer that helps you build and scale complex, high-performance asynchronous and parallel software. It provides a powerful abstraction for dealing with side effects, concurrency, and resource management in a functional way. We’ll focus on practice, but I highly encourage you to read through the Cats Effect docs to better understand the fundamentals.
Understanding IO
When working with Cats Effect, you’ll use the IO
type extensively. Here’s the key insight: IO
doesn’t run your code immediately – it creates a blueprint for code that will run later.
Think of it this way:
- Writing
println("Hello")
immediately prints to the console. - Writing
IO(println("Hello"))
creates a description of printing to the console, but doesn’t actually print anything until you run it (e.g., withIOApp
that we’ll create in the next paragraph).
This separation is powerful because:
- You can compose operations before running them.
- You control when effects happen.
- You get consistent error handling (
IO
captures exceptions and lets you handle them functionally). - You can safely work with concurrent operations.
IOApp
To use Cats Effect in our app, we need to update Main.scala
and wrap our program in an IOApp.Simple
:
package dev.kamgy
import cats.effect.{IO, IOApp}
object Main extends IOApp.Simple {
override val run: IO[Unit] = {
IO.println("Hello, world!")
}
}
IOApp
takes care of properly setting up and tearing down the runtime needed to run IO
operations. This also avoids the need to run IO.unsafeRunSync
on your own.
You should be able to run this code and see Hello, world!
printed to the console.
As we mentioned previously, IO
allows you to compose multiple operations together. To demonstrate this, we can update Main.scala
to:
package dev.kamgy
import scala.concurrent.duration.DurationInt
import cats.effect.{IO, IOApp}
object Main extends IOApp.Simple {
override val run: IO[Unit] =
for {
_ <- IO.println("Creating resources...")
_ <- IO.sleep(500.millis)
_ <- IO.println("Resources created successfully!")
_ <- IO.println("Starting the application...")
_ <- IO.sleep(500.millis)
_ <- IO.println("Application started successfully!")
} yield ()
}
Summary
In this part, we laid the foundation for connecting our Idle RPG game to the outside world by introducing Cats Effect.
We’re now equipped with the tools to safely handle external interactions like database operations, HTTP requests, and file operations.
In the next part, we’ll work on storing data in a database.
As always, you can find the complete code in the GitHub repository .