Menjembatani dunia pure dan dunia nyata dengan effect system: IO dari cats-effect dan ZIO sebagai deskripsi program yang dapat dikomposisi, resource safety lewat Resource dan Scope, serta cancellation yang bersih — plus kapan memilih library versus full-stack effect.

Sejauh ini, kalian belajar bahwa Scala menyukai pure function: input sama → output sama. Tapi program sungguhan harus membaca file, memanggil API, dan menulis ke database — semua operasi yang punya efek samping. Bagaimana kalian mempertahankan kemurnian di dunia yang tidak murni? Jawabannya: effect system.
Mengapa ini penting? Karena effect system adalah inti dari "functional programming di produksi". Library seperti cats-effect dan ZIO membuat efek samping menjadi nilai yang bisa dikomposisi — bisa dibangun, diprogram, dibatalkan, dan diuji dengan aman. Setelah episode ini, kalian akan melihat dunia backend Scala dengan mata yang berbeda.
Bandingkan dua cara menulis efek samping:
def readFile(path: String): String =
val source = scala.io.Source.fromFile(path)
try source.mkString finally source.close()Masalahnya: fungsi ini tidak pure. Ia membaca disk, bergantung pada sistem, bisa melempar exception — dan kalian tidak bisa menundanya, membatalkannya, atau mengujinya tanpa benar-benar membaca file.
Effect system membalik logika: kalian tidak "menjalankan" efek saat menulis kode, kalian mendeskripsikan efek sebagai nilai, lalu menjalankannya nanti di satu titik.
IO[A] adalah deskripsi dari komputasi yang ketika dijalankan akan menghasilkan A:
import cats.effect.IO
val program: IO[String] =
IO.println("mulai...") *>
IO.readLine.map(name => s"Hello, $name!")
// belum menjalankan apa pun sampai ini:
program.unsafeRunSync()Perhatikan: IO.println dan IO.readLine tidak mencetak/membaca saat dipanggil — mereka membangun deskripsi. Eksekusi terjadi hanya saat unsafeRunSync(). Ini membuka kemungkinan:
Operator penting di IO:
import cats.effect.IO
import cats.syntax.all.*
val readConfig: IO[Map[String, String]] = IO.pure(Map("port" -> "8080"))
val readDb: IO[Int] = IO.pure(42)
val combined: IO[String] =
for
cfg <- readConfig
count <- readDb
yield s"config ${cfg("port")}, total $count"for-comprehension (sintaks gula untuk flatMap) membuat komposisi program multi-langkah terbaca seperti kode imperatif, tetapi tetap pure.
Efek samping membutuhkan pembersihan — file harus ditutup, koneksi harus dilepas. Cats-effect menyediakan Resource:
import cats.effect.{IO, Resource}
def openFile(path: String): Resource[IO, String] =
Resource.make(
IO { scala.io.Source.fromFile(path).mkString }
)(content => IO.println(s"cleanup file $path"))
val program: IO[Unit] =
openFile("data.txt").use { content =>
IO.println(s"file berisi ${content.length} karakter")
}Kontrak Resource.make(acquire)(release) menjamin release dipanggil — baik sukses, gagal, maupun dibatalkan. Ini menghilangkan kelas bug "lupa tutup koneksi". ZIO memiliki mekanisme setara bernama Scope.
Kekuatan lain effect system: cancellation yang bersih. Program yang berjalan bisa dibatalkan — misalnya karena timeout — dan effect system memastikan resource tetap dibersihkan:
import cats.effect.{IO, IOApp}
import scala.concurrent.duration.*
object Main extends IOApp.Simple:
val run: IO[Unit] =
for
fiber <- IO.sleep(10.seconds).start // mulai di fiber terpisah
_ <- IO.sleep(1.second) // tunggu 1 detik
_ <- fiber.cancel // batalkan sebelum selesai
_ <- IO.println("dibatalkan")
yield ()Konsep fiber di sini akan kita bedah penuh di episode 12. Yang penting sekarang: pembatalan tidak meninggalkan resource menggantung.
ZIO adalah effect system lain yang lebih "full-stack": ia membawa environment, error, dan effect dalam satu tipe:
import zio.*
object Main extends ZIOAppDefault:
def run =
for
_ <- Console.printLine("Siapa nama kalian?")
name <- Console.readLine
_ <- Console.printLine(s"Hello, $name!")
yield ()ZIO[R, E, A] punya tiga parameter: R (environment/dependency), E (error), dan A (hasil). Error di-level tipe — bukan exception yang lolos. ZIOAppDefault menyediakan entry point dengan runtime terkelola.
| Aspek | cats-effect | ZIO |
|---|---|---|
| Posisi | Library — typeclass (Sync, Async) | Framework lengkap dengan runtime sendiri |
| Tipe | IO[A] | ZIO[R, E, A] |
| Konsep | Efek polos, kompatibel luas | Environment, error, dan effect dalam satu tipe |
| Ekosistem | http4s, fs2, doobie (typelevel) | zio-http, zio-streams, quill |
| Fokus | Interop dengan library FP lain | Satu stack terintegrasi |
Tip
Pilih berdasarkan kebutuhan: jika kalian ingin kebebasan memilih library per lapisan, cats-effect (ekosistem Typelevel) lebih fleksibel. Jika ingin satu stack konsisten dengan environment injection bawaan, ZIO mengurangi keputusan arsitektur. Keduanya adalah pilihan produksi yang matang — pilih sesuai selera tim, bukan "yang paling baru".
Jawaban praktis: semua program yang melakukan I/O — HTTP server, CLI yang membaca input, aplikasi database. Mulai dari proyek kecil pun, membungkus efek dalam IO/ZIO sejak awal lebih murah daripada merombak nanti. Untuk komputasi murni (memproses List), effect system tidak perlu — di situlah map/filter biasa bekerja.
unsafeRunSync di mana-mana — efek harus dikomposisi dan dijalankan sekali di entry point; jangan membongkar IO di tengah alur.flatMap berantai tanpa for — untuk 3+ langkah, for-comprehension jauh lebih terbaca.Resource/Scope — resource tanpa akuisisi-terkelola adalah potensi leak di produksi.Inti yang harus dibawa pulang:
IO[A] dan ZIO[R, E, A] adalah dua implementasi utama.Resource (cats-effect) dan Scope (ZIO) menjamin cleanup otomatis.Di episode 12 selanjutnya, kita masuk ke ranah yang menantang: concurrency dan parallelism — Future, fiber, Ref, Queue, dan structured concurrency. Sampai jumpa di episode 12!