पाठ 17 / 25
Effect Systems: Cats Effect and ZIO
Understand why functional effect systems exist and how they model programs.
Programs as values
Effect systems represent side effects (I/O, time, randomness, concurrency) as values describing what to do, which run only when the program is executed at the "end of the world". The two leading libraries are Cats Effect with its IO type (used with Cats, http4s, fs2 and Doobie) and ZIO with ZIO[R, E, A] (an environment, a typed error and a result). Both offer lazy, referentially transparent effects, so the same IO value can be run, retried or timed out repeatedly. Both provide fibers: lightweight green threads, so you can run hundreds of thousands of concurrent tasks, with structured concurrency, safe cancellation and resource safety (Resource or ZIO.acquireRelease guarantee cleanup). They also include retry and schedule combinators, queues, refs and streaming (fs2 and ZIO Streams). The trade-off is a learning curve and a different programming style. For many services, effect systems provide predictable concurrency and error handling; for simpler applications, Futures or plain synchronous code on virtual threads may be enough.
The same workflow in Cats Effect and ZIO
Lazy descriptions of effects with timeouts, retries and parallelism.
// Cats Effect
import cats.effect.{IO, IOApp}
import cats.syntax.all.*
import scala.concurrent.duration.*
object PricingApp extends IOApp.Simple:
def fetchPrice(sku: String): IO[Long] =
IO.println(s"fetching $sku") *> IO.sleep(100.millis).as(4950L)
val run: IO[Unit] =
for
prices <- List("pen", "ink", "pad").parTraverse(fetchPrice) // run in parallel on fibers
total <- IO.pure(prices.sum)
_ <- IO.println(s"total: $total")
yield ()
// retry and timeout are combinators on the description:
val robust: IO[Long] = fetchPrice("pen").timeout(2.seconds).handleErrorWith(_ => IO.pure(0L))
// ZIO equivalent (in a separate file)
// import zio.*
// object PricingZio extends ZIOAppDefault:
// def fetchPrice(sku: String): UIO[Long] = ZIO.succeed(4950L).delay(100.millis)
// def run = ZIO.foreachPar(List("pen", "ink", "pad"))(fetchPrice)
// .map(_.sum)
// .flatMap(total => Console.printLine(s"total: $total"))A screenplay versus a live performance
An IO value is a screenplay: writing it does not make anything happen, and you can copy it, edit it or add "if an actor is late, retry the scene". Only when the director shouts action (the runtime) is it performed.
त्वरित जाँच: What is a key difference between Cats Effect IO and a standard Future?
- IO cannot run concurrently
- IO is a lazy description that runs only when executed and can be re-run, while a Future starts eagerly
- Future supports cancellation but IO does not
- There is no difference
Answer
IO is a lazy description that runs only when executed and can be re-run, while a Future starts eagerly — Effects are lazy and referentially transparent; Futures start immediately and memoise their result.