Enum (enumeration) pada Scala 3 mendefinisikan sebuah tipe yang himpunan nilainya tertutup, yaitu hanya berisi case (varian) yang dideklarasikan pada definisi tipe itu. Dengan sifat tertutup ini, enum menjadi bahan utama tipe data aljabar (algebraic data type, ADT), yaitu tipe yang dibangun dari beberapa varian yang masing-masing membawa datanya sendiri. Topik ini membahas enum bernilai tunggal, enum dengan parameter, enum rekursif untuk pohon ekspresi, pola pencocokannya, dan hubungannya dengan gaya sealed trait yang lebih lama.
Enum dengan nilai tunggal#
Enum yang paling sederhana berisi case tanpa parameter, sehingga setiap case adalah satu nilai tunggal dari tipe tersebut. Definisinya menyerupai deklarasi konstanta yang dikelompokkan dalam satu tipe, dan compiler mengetahui daftar lengkapnya karena tidak ada cara lain untuk membuat nilai di luar case tersebut.
Simpan program berikut sebagai status-tugas.scala.
// Enum sederhana: himpunan nilai yang tertutup
enum StatusTugas:
case BelumDimulai, Berjalan, Selesai
@main def demoStatusTugas(): Unit =
val tugas = StatusTugas.Berjalan
println(StatusTugas.values.mkString(", "))
tugas match
case StatusTugas.BelumDimulai => println("tugas belum dikerjakan")
case StatusTugas.Berjalan => println("tugas sedang dikerjakan")
case StatusTugas.Selesai => println("tugas telah selesai")Jalankan program tersebut dengan scala-cli.
# mengompilasi dan menjalankan program enum sederhana
scala-cli run status-tugas.scalaBelumDimulai, Berjalan, Selesai
tugas sedang dikerjakanMetode values mengembalikan seluruh case dalam urutan deklarasinya, sehingga daftar status dapat dicetak tanpa menuliskannya ulang. Pencocokan pada tugas mencakup ketiga case, dan apabila salah satu case terlewat, compiler memberi peringatan match may not be exhaustive karena himpunan kemungkinan nilai tipe tersebut sudah diketahui. Jaminan inilah yang membedakan enum dari konstanta String atau Int yang tersebar tanpa induk tipe.
Enum dengan parameter#
Case dengan parameter bekerja seperti case class kecil yang dideklarasikan di dalam enum. Setiap parameter memiliki tipe, sehingga satu varian dapat membawa data yang berbeda dari varian lainnya. Nilainya dibangun dengan nama enum sebagai awalan, misalnya Hasil.Sukses(42).
Simpan program berikut sebagai hasil-penguraian.scala.
// Enum dengan parameter: setiap case membawa datanya sendiri
enum Hasil:
case Sukses(data: Int)
case Gagal(pesan: String)
def uraikan(teks: String): Hasil =
teks.toIntOption match
case Some(angka) => Hasil.Sukses(angka)
case None => Hasil.Gagal(s"\"$teks\" bukan bilangan bulat")
@main def hasilPenguraian(): Unit =
for teks <- List("42", "tujuh") do
println(uraikan(teks))
uraikan("42") match
case Hasil.Sukses(data) => println(s"berhasil diurai: $data")
case Hasil.Gagal(pesan) => println(pesan)Jalankan program tersebut dengan scala-cli.
# mengompilasi dan menjalankan program enum berparameter
scala-cli run hasil-penguraian.scalaSukses(42)
Gagal("tujuh" bukan bilangan bulat)
berhasil diurai: 42Fungsi uraikan mengembalikan tipe Hasil, bukan Int atau String, sehingga keberhasilan dan kegagalan penguraian tersimpan dalam tipe yang sama. Pencocokan case Hasil.Sukses(data) mengikat data yang dibawa varian ke nama lokal data, dan case Hasil.Gagal(pesan) melakukan hal yang sama terhadap pesan error. Kedua case dituliskan berdampingan karena compiler memberi peringatan apabila salah satunya terlewat.
Parameter tipe juga dapat menyertai enum, misalnya enum Opsi[+A] dengan case Ada(nilai: A), sehingga satu enum menampung tipe isi yang berbeda-beda. Pola pada enum semacam itu tetap ditulis case demi case, dan tipe A mengikuti tipe nilai yang disimpannya.
Enum rekursif sebagai ADT#
Sifat tertutup menjadi terasa lengkap ketika salah satu variannya merujuk tipe enum itu sendiri. Struktur semacam ini disebut rekursif dan lazim digunakan untuk pohon, daftar tautan, dan ekspresi aritmetika. Enum Ekspresi berikut memiliki satu case dasar berupa Angka dan dua case rekursif yang merangkai dua subekspresi.
Simpan program berikut sebagai ekspresi-aritmetika.scala.
// Enum rekursif: pohon ekspresi aritmetika sebagai ADT
enum Ekspresi:
case Angka(n: Int)
case Tambah(kiri: Ekspresi, kanan: Ekspresi)
case Kali(kiri: Ekspresi, kanan: Ekspresi)
import Ekspresi.*
def evaluasi(e: Ekspresi): Int = e match
case Angka(n) => n
case Tambah(k, r) => evaluasi(k) + evaluasi(r)
case Kali(k, r) => evaluasi(k) * evaluasi(r)
@main def ekspresiAritmetika(): Unit =
val e = Tambah(Angka(2), Kali(Angka(3), Angka(4)))
println(e)
println("nilai: " + evaluasi(e))Jalankan program tersebut dengan scala-cli.
# mengompilasi dan menjalankan program pohon ekspresi
scala-cli run ekspresi-aritmetika.scalaTambah(Angka(2),Kali(Angka(3),Angka(4)))
nilai: 14Definisi evaluasi mengikuti bentuk tipe Ekspresi case demi case. Case dasar menghentikan rekursi dengan mengembalikan angkanya, sedangkan case rekursif memanggil evaluasi terhadap kedua subekspresi lalu menggabungkan hasilnya. Tidak ada case lain di luar Ekspresi, sehingga fungsi evaluasi tidak memerlukan cabang untuk bentuk tak terduga, dan compiler memberi peringatan apabila satu case terlewat. Baris pertama keluaran menampilkan hasil bawaan toString dari pohon tersebut, yaitu konstruktor yang bersarang mengikuti struktur datanya.
Pola pada enum#
Pencocokan enum menggunakan pola konstruktor per case, yang ditulis dengan nama enum sebagai awalan atau setelah import seluruh case. Bentuk dengan import lebih ringkas pada berkas yang memang berfokus pada satu enum.
import Hasil.*
def deskripsi(h: Hasil): String = h match
case Sukses(data) => s"berhasil: $data"
case Gagal(pesan) => s"gagal: $pesan"Compiler memberi peringatan, bukan error, ketika satu case tidak tercakup oleh pola. Peringatan match may not be exhaustive menyebut case yang terlewat, misalnya Hasil.Gagal(_), dan pencocokan yang diteruskan tanpa cabang tambahan dapat melempar MatchError saat program berjalan. Perlakukan peringatan tersebut sebagai hasil pemeriksaan yang disediakan oleh tipe tertutup, lalu perbaiki polanya atau tulis cabang yang sengaja mencakup sisanya.
Relasi dengan sealed trait#
Sebelum Scala 3, ADT ditulis dengan sealed trait yang diperluas oleh case class dalam berkas yang sama. Gaya itu tetap sah dan setara dari sisi tipe, yaitu hierarki tertutup yang exhaustiveness-nya diperiksa compiler.
sealed trait Bentuk
case class Persegi(sisi: Double) extends Bentuk
case class Lingkaran(radius: Double) extends BentukPerbandingan kedua gaya dirangkum pada tabel berikut.
| Aspek | sealed trait + case class | enum |
|---|---|---|
| Letak varian | dideklarasikan terpisah di tingkat atas | dideklarasikan di dalam enum |
| Panjang definisi | lebih panjang | lebih ringkas |
| Pemeriksaan exhaustiveness | ada, karena hierarki sealed | ada, karena himpunan nilai tertutup |
| Penggunaan | kode lama dan hierarki yang membutuhkan kelas mandiri | pilihan bawaan untuk ADT baru |
Kedua gaya dapat bersandingan karena enum dapat memperluas sealed trait yang sudah ada, misalnya enum Poligon extends Sisi. Gunakan enum sebagai bentuk bawaan untuk ADT baru, dan pertahankan sealed trait apabila setiap varian membutuhkan definisi kelas yang berdiri sendiri, misalnya companion object tersendiri atau kode lama yang sudah berjalan.
Ringkasan#
- Enum mendefinisikan tipe yang himpunan nilainya tertutup, mulai dari case bernilai tunggal hingga case berparameter yang membawa data.
- Varian yang merujuk tipe enum itu sendiri membentuk ADT rekursif seperti pohon ekspresi, dan pencocokannya ditulis case demi case.
- Case yang terlewat pada pencocokan memicu peringatan exhaustiveness, dan case yang tersisa tetap ditangani secara eksplisit.
sealed traitdengan case class masih sah dan setara dari sisi tipe, sedangkan enum menjadi bentuk yang lebih ringkas untuk ADT baru.
Referensi#
- Enums — Scala 3 Reference — Sintaks enum, case berparameter, dan penurunan dari sealed trait.
- Algebraic Data Types — Scala 3 Reference — Enum sebagai ADT dan sifat tertutupnya.
- Domain Modeling Tools — Scala 3 Book — Enum dan case class dalam pemodelan domain.