Cats Effect 3 en Scala: IO monad, fibers, Resource y programación funcional tipada

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

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP