Effect Systems

Effect systems describe side effects as lazy, composable values, giving referential transparency, structured concurrency, resource safety, and fiber-based (green thread) concurrency.

Effect systems describe side effects as lazy, composable values, giving referential transparency, structured concurrency, resource safety, and fiber-based (green thread) concurrency.

34.1 Cats Effect 3

IO.delay/IO.blocking/IO.interruptible — suspend side effects.

Resource — safe acquisition/release (Resource.make(acquire)(release), .use).

Ref (atomic state), Deferred (one-shot promise), Queue, Semaphore, Supervisor.

Cancellation, timeouts (.timeout), retries, handleErrorWith, attempt.

Typeclass hierarchy: MonadCancel, Spawn, Concurrent, Temporal, Sync, Async.

//> using dep org.typelevel::cats-effect:3.5.7
import cats.effect.*
import cats.syntax.all.*
import scala.concurrent.duration.*

object Main extends IOApp.Simple:
  val hello: IO[Unit] = IO.println("Hello")

  def countdown(n: Int): IO[Unit] =
    if n == 0 then IO.println("Liftoff!")
    else IO.println(n) >> IO.sleep(1.second) >> countdown(n - 1)

  def run: IO[Unit] =
    for
      _ <- hello
      fiber <- countdown(3).start             // run concurrently on a fiber
      _ <- IO.println("doing other work")
      _ <- fiber.join
      results <- List(1, 2, 3).parTraverse(n => IO.sleep(100.millis).as(n * 2))
      _ <- IO.println(results)
      winner <- IO.race(IO.sleep(1.second).as("slow"), IO.sleep(10.millis).as("fast"))
      _ <- IO.println(winner)
    yield ()
val fileResource: Resource[IO, scala.io.Source] =
  Resource.fromAutoCloseable(IO.blocking(scala.io.Source.fromFile("data.txt")))

val lines: IO[List[String]] = fileResource.use(src => IO.blocking(src.getLines().toList))

val counter: IO[Int] = for
  ref <- Ref.of[IO, Int](0)
  _ <- (1 to 100).toList.parTraverse_(_ => ref.update(_ + 1))
  n <- ref.get
yield n

34.2 ZIO 2

ZIO[R, E, A]: needs environment R, may fail with E, succeeds with A. Features: ZLayer dependency injection, typed errors, fibers, Ref, Queue, Hub, STM (software transactional memory), Schedule (retries), ZIO Streams, ZIO Test, ZIO HTTP, ZIO Config, ZIO JSON.

//> using dep dev.zio::zio:2.1.14
import zio.*

object Main extends ZIOAppDefault:
  trait UserRepo:
    def find(id: Int): IO[String, String]

  object UserRepo:
    val live: ULayer[UserRepo] = ZLayer.succeed(new UserRepo:
      def find(id: Int) = if id == 1 then ZIO.succeed("Ann") else ZIO.fail(s"no user $id"))

  val program: ZIO[UserRepo, String, Unit] =
    for
      repo <- ZIO.service[UserRepo]
      name <- repo.find(1)
      _ <- Console.printLine(s"Found $name").orDie
      _ <- ZIO.foreachPar(1 to 3)(i => ZIO.sleep(100.millis) *> ZIO.succeed(i))
    yield ()

  def run = program.provide(UserRepo.live).catchAll(e => Console.printLine(s"Error: $e"))

34.3 Kyo & Ox

Kyo — algebraic-effects-style library with composable effect types.

Ox (SoftwareMill) — direct-style structured concurrency on JDK 21 virtual threads (par, race, supervised, channels).

34.4 Choosing

standard library; fine for simple async tagless final / IO Typelevel ecosystem (http4s, fs2, doobie, skunk) ZIO[R, E, A] all-in-one batteries included ecosystem virtual threads, minimal abstraction