Option

Memodelkan ketiadaan nilai secara eksplisit tanpa pemeriksaan null.

Daftar isi

Scala memodelkan ketiadaan nilai sebagai nilai bertipe Option, sehingga kemungkinan kosongnya sebuah hasil terbaca langsung dari tipe kembalian fungsi. Topik ini membahas konstruktor Some dan None, pembuatan Option dari pencarian yang mungkin gagal seperti Map.get dan find, penyambungan beberapa langkah dengan map dan flatMap, serta pembacaan hasilnya dengan getOrElse dan pola match (pattern matching). Setelah topik ini dibaca, kode yang menangani data yang mungkin tidak ada dapat ditulis tanpa pemeriksaan null sama sekali.

Some dan None#

Option[A] adalah tipe yang menyatakan sebuah nilai bertipe A mungkin ada atau tidak ada. Tipe ini memiliki tepat dua bentuk, yaitu Some[A] yang membungkus nilai yang ada dan objek None yang menyatakan ketiadaan nilai. Karena kedua bentuk tersebut berasal dari satu tipe yang sama, compiler memaksa kode yang menggunakan hasilnya untuk mempertimbangkan kedua kemungkinan itu. Ketidakhadiran tidak lagi tersembunyi di balik nilai khusus, melainkan menjadi bagian dari tipe kembalian yang dinyatakan secara eksplisit.

Nilai Option paling sering berasal dari pencarian yang mungkin gagal. Metode get pada Map mengembalikan Option[V], bukan langsung V, karena kunci yang dicari belum tentu ada. Metode find pada koleksi mengembalikan Option[A] karena elemen yang memenuhi predikat belum tentu ditemukan. Program berikut memperlihatkan keduanya.

scala option-dasar.scala
// Option: ketiadaan nilai menjadi bagian tipe kembalian
@main def optionDasar(): Unit =
  val nilai: Map[String, Int] = Map("Ali" -> 88, "Citra" -> 92, "Budi" -> 75)

  val a: Option[Int] = nilai.get("Citra")
  val b: Option[Int] = nilai.get("Dewi")

  println(a)
  println(b)

  val angka = List(12, 7, 20, 5)
  val genapPertama: Option[Int] = angka.find(n => n % 2 == 0)

  println(genapPertama)
bash
# menjalankan program option-dasar.scala
scala-cli run option-dasar.scala
text Keluaran
Some(92)
None
Some(12)

Keluaran memperlihatkan bahwa ketiga pencarian menghasilkan tipe yang sama, yaitu Option[Int]. Pencarian nilai “Citra” menghasilkan Some(92) karena kuncinya ada, sedangkan pencarian “Dewi” menghasilkan None karena kuncinya tidak terdaftar. Pencarian bilangan genap pertama menghasilkan Some(12) karena elemen pertama List(12, 7, 20, 5) sudah memenuhi predikatnya. Dengan tipe kembalian seperti ini, pembaca tanda tangan metode langsung mengetahui bahwa hasilnya perlu diperiksa sebelum digunakan.

Menyambung langkah dengan map dan flatMap#

Option dapat ditransformasikan tanpa membongkar isinya terlebih dahulu. Metode map menerapkan fungsi pada isi Some dan membiarkan None tetap None, sehingga cabang ketiadaan tidak perlu ditulis ulang pada setiap langkah. Apabila langkah berikutnya sendiri mengembalikan Option, metode flatMap menyambung kedua langkah itu tanpa menimbulkan Some(Some(...)) yang bertumpuk. Terakhir, getOrElse menyediakan nilai cadangan yang digunakan apabila hasilnya None, sehingga rantai selalu berakhir pada nilai biasa.

Program berikut menyambung tiga langkah tersebut pada data nilai ujian yang sama.

scala rantai-option.scala
// Rantai transformasi Option dengan map, flatMap, dan getOrElse
@main def rantaiOption(): Unit =
  val kelas: Map[String, Int] = Map("Ali" -> 88, "Citra" -> 92)

  // map mengubah isi Some tanpa menyentuh None
  val nilaiNaik: Option[Int] = kelas.get("Ali").map(n => n + 5)
  println(nilaiNaik)

  // flatMap menyambung langkah yang hasilnya juga Option
  def predikat(n: Int): Option[String] =
    if n >= 90 then Some("sangat baik") else Some("baik")

  val predikatAli = kelas.get("Ali").flatMap(predikat)
  println(predikatAli)

  // getOrElse menyediakan nilai pengganti ketika hasilnya None
  val predikatDewi = kelas.get("Dewi").flatMap(predikat).getOrElse("tanpa nilai")
  println(predikatDewi)
bash
# menjalankan program rantai-option.scala
scala-cli run rantai-option.scala
text Keluaran
Some(93)
Some(baik)
tanpa nilai

Baris pertama keluaran menunjukkan bahwa map menambahkan lima pada isi Some(88) sehingga menjadi Some(93). Baris kedua menunjukkan bahwa flatMap menyambung get dengan fungsi predikat yang juga mengembalikan Option, dan hasilnya tetap satu lapisan. Baris ketiga memperlihatkan kasus None, karena “Dewi” tidak ada pada Map tersebut, sehingga getOrElse mengembalikan teks cadangan “tanpa nilai”. Satu rantai seperti ini menggantikan rangkaian pemeriksaan kosong yang bersarang dan biasanya muncul pada gaya imperatif.

Pola match pada Option#

Pola match membedah nilai Option menurut bentuknya, yaitu kasus Some(isi) untuk nilai yang ada dan kasus None untuk ketiadaan nilai. Bentuk ini digunakan apabila kedua cabang membutuhkan perlakuan yang berbeda hingga ke strukturnya, bukan hanya transformasi sederhana pada isinya. Compiler memeriksa kelengkapan kasus, sehingga match yang tidak menangani None ditolak pada waktu kompilasi.

Program berikut menerjemahkan hasil pencarian arsip menjadi teks deskripsi.

scala pola-match.scala
// Pola match pada Option memisahkan kasus Some dan None
@main def polaMatchOption(): Unit =
  val arsip: Map[String, String] =
    Map("dokumen-1" -> "laporan", "dokumen-2" -> "catatan")

  def deskripsikan(kunci: String): String =
    arsip.get(kunci) match
      case Some(isi) => s"$kunci berisi $isi"
      case None      => s"$kunci tidak ditemukan"

  println(deskripsikan("dokumen-2"))
  println(deskripsikan("dokumen-9"))
bash
# menjalankan program pola-match.scala
scala-cli run pola-match.scala
text Keluaran
dokumen-2 berisi catatan
dokumen-9 tidak ditemukan

Kedua baris keluaran berasal dari dua kasus yang berbeda pada pola match yang sama. Kunci “dokumen-2” ditemukan, sehingga cabang Some(isi) menyusun teks dengan isinya, sedangkan kunci “dokumen-9” jatuh ke cabang None. Karena fungsi deskripsikan wajib mengembalikan String, kedua kasus itu harus menghasilkan nilai bertipe sama, dan pemeriksaan kelengkapannya dilakukan compiler.

Mengapa null dihindari#

Null bukanlah bagian dari model data Scala, melainkan keadaan Java yang dititipkan ke JVM dan tetap hidup demi keserasian dengan pustaka Java. Pada kode Java, setiap rujukan berpotensi berisi null, sehingga pemeriksaan kosong harus diulang di banyak tempat dan satu pemeriksaan yang terlewat memicu NullPointerException pada waktu berjalan. Scala memindahkan kemungkinan itu ke dalam tipe: rujukan biasa dianggap selalu berisi, sedangkan kemungkinan kosong dinyatakan melalui Option. Dengan pemisahan ini, yang memaksa penanganan None adalah compiler, bukan kedisiplinan pembaca kode.

Nilai null tetap dapat muncul di satu tempat yang sah, yaitu batas integrasi dengan pustaka Java. Konstruktor Option(...) mengubah nilai yang mungkin null menjadi Some atau None, sehingga null tidak merambat masuk ke kode Scala lebih jauh.

Ringkasan#

  • Option[A] memiliki dua bentuk, yaitu Some[A] yang membungkus nilai yang ada dan None yang menyatakan ketiadaan nilai.
  • Pencarian yang mungkin gagal seperti Map.get dan find mengembalikan Option, sehingga kemungkinan kosong terbaca dari tipe kembaliannya.
  • map dan flatMap menyambung transformasi tanpa pencabangan manual, sedangkan getOrElse memberi nilai cadangan untuk kasus None.
  • Null adalah keadaan Java yang dititipkan ke JVM, dan Scala menggantikannya dengan Option sehingga penanganan ketiadaan nilai diperiksa compiler.

Referensi#