Cats Effect 3 es el runtime de efectos del ecosistema Typelevel en Scala. Llegó en marzo de 2021, casi al mismo tiempo que Scala 3, y ha ido consolidándose como la base de una familia de librerías que incluye http4s, Doobie, fs2 y muchas más. Si ZIO es más self-contained (trae todo bajo su propio paraguas), Cats Effect es más modular: cada librería hace una cosa y las puedes combinar a tu gusto.
IO[A]: el tipo central
En Cats Effect, el tipo que envuelve los efectos es IO[A]. Un valor de tipo IO[A] describe una computación que, cuando se ejecute, producirá un valor de tipo A (o fallará con una excepción). Hasta que no llames a unsafeRunSync o unsafeRunAsync, nada se ejecuta:
import cats.effect.*
val hola: IO[Unit] = IO.println("Hola desde Cats Effect")
val numero: IO[Int] = IO.pure(42)
val conError: IO[String] = IO.raiseError(new RuntimeException("algo fue mal"))
.handleErrorWith(e => IO.pure(s"Error capturado: ${e.getMessage}"))
object Main extends IOApp.Simple:
def run: IO[Unit] = hola *> numero.flatMap(n => IO.println(s"El número es: $n"))
La diferencia con Future de Scala es que IO es lazy: defines qué se hace, no lo ejecutas inmediatamente. Esto permite composición, reintentos y cancelación de forma limpia.
Fibers en Cats Effect 3
Cats Effect 3 introdujo las fibers como concepto de primera clase, similar a las de ZIO pero con una API algo distinta. Las fibers son computaciones ligeras que se pueden lanzar, cancelar y unir:
import cats.effect.*
val tarea1: IO[String] = IO.sleep(100.milliseconds) *> IO.pure("primera")
val tarea2: IO[String] = IO.sleep(50.milliseconds) *> IO.pure("segunda")
val programa: IO[Unit] = for
fiber1 <- tarea1.start
fiber2 <- tarea2.start
r1 <- fiber1.joinWithNever
r2 <- fiber2.joinWithNever
_ <- IO.println(s"Resultados: $r1, $r2")
yield ()
Para ejecutar dos efectos en paralelo y quedarse con ambos resultados, la forma idiomática es parTupled o parMapN:
import cats.syntax.all.* val resultado: IO[(String, String)] = (tarea1, tarea2).parTupled
Resource: gestión de ciclos de vida
Una de las partes más útiles de Cats Effect es Resource[IO, A], que garantiza que los recursos se liberan aunque haya errores:
import cats.effect.*
def abrirConexion(url: String): IO[Connection] = IO.delay(???)
def cerrarConexion(c: Connection): IO[Unit] = IO.delay(c.close())
val conexion: Resource[IO, Connection] =
Resource.make(abrirConexion("jdbc:postgresql://..."))(cerrarConexion)
conexion.use { conn =>
IO.delay(conn.prepareStatement("SELECT 1").execute())
}
// La conexión se cierra al salir del bloque use, siempre
Esto es equivalente a un try-with-resources de Java pero composable: puedes combinar varios Resource con .flatMap o con for comprehensions.
fs2: streaming funcional
fs2 (Functional Streams for Scala) es la librería de streaming del ecosistema Typelevel, construida sobre Cats Effect. Los Stream[IO, A] son lazy, con backpressure y cancelables:
import cats.effect.* import fs2.Stream val numeros: Stream[IO, Int] = Stream.range(1, 100) val pares: Stream[IO, Int] = numeros .filter(_ % 2 == 0) .take(10) val imprimir: IO[Unit] = pares .evalMap(n => IO.println(n)) .compile.drain
fs2 también integra muy bien con Kafka (fs2-kafka), con bases de datos via Doobie o con HTTP via http4s, formando una pila funcional coherente.
traverse y parTraverse
Una operación que aparece constantemente en código con Cats es traverse: aplicar un efecto a cada elemento de una colección y combinar los resultados:
import cats.effect.*
import cats.syntax.all.*
val urls = List("https://ejemplo1.com", "https://ejemplo2.com", "https://ejemplo3.com")
def fetch(url: String): IO[String] = IO.pure(s"contenido de $url")
// Secuencial
val secuencial: IO[List[String]] = urls.traverse(fetch)
// En paralelo
val paralelo: IO[List[String]] = urls.parTraverse(fetch)
La diferencia entre traverse y parTraverse es el nivel de concurrencia. parTraverse lanza todas las fibers a la vez; traverse las ejecuta una tras otra.
Cats Effect vs ZIO en 2026
Cats Effect tiene una curva de aprendizaje más pronunciada porque trabaja mucho con typeclasses y la abstracción es mayor. Pero esa abstracción tiene ventajas: las librerías que se construyen sobre Cats Effect son intercambiables si usas las interfaces correctas. Puedes cambiar de http4s a otro servidor sin tocar el código de tu lógica de negocio si lo has escrito contra las typeclasses de Cats Effect.
Para equipos con experiencia en programación funcional o que vienen de Haskell, Cats Effect suele encajar mejor. Para equipos nuevos en Scala funcional o que prefieren una API más directa, ZIO 2 puede ser más accesible.
Lo que está claro es que en 2026 cualquier aplicación Scala seria usa uno de los dos. El modelo de Future sin efectos controlados ya no es la opción por defecto.
Imagen: Pexels / Markus Spiske
