Discriminated union (DU) adalah tipe yang nilainya terbatas pada sejumlah kasus, dan setiap kasus membawa bentuk data miliknya sendiri. Topik ini membahas pendekatan tipe sum untuk memodelkan alternatif domain, cara mengonstruksi nilai DU, konsumsinya lewat pattern matching, serta peringatan compiler atas kasus yang belum ditangani. Contoh yang digunakan berasal dari bentuk geometri dan status peminjaman buku di perpustakaan.
Tipe sum: satu kasus, satu bentuk data#
Record menuntut seluruh field terisi pada setiap nilainya, sedangkan DU memilih satu kasus dari beberapa alternatif. Bentuk geometri memperlihatkan perbedaan itu dengan jelas: lingkaran hanya memerlukan radius, persegi hanya memerlukan sisi, dan segitiga memerlukan pasangan alas dan tinggi. Satu record dengan semua field sekaligus akan memuat field yang tidak berarti untuk sebagian kasus, sedangkan DU mengikat setiap alternatif dengan datanya masing-masing.
Deklarasi DU menggunakan kata kunci type diikuti daftar kasus yang dipisahkan |. Kasus tanpa data cukup ditulis dengan namanya, dan kasus berdata menuliskan bentuk datanya setelah kata of. Nama pada kasus berdata bersifat pilihan dan berguna untuk mendokumentasikan maksud setiap data.
// Bentuk geometri: satu kasus, satu bentuk data.
type Bentuk =
| Lingkaran of radius: float
| Persegi of sisi: float
| Segitiga of alas: float * tinggi: float
let luas bentuk =
match bentuk with
| Lingkaran r -> System.Math.PI * r * r
| Persegi s -> s * s
| Segitiga (a, t) -> a * t / 2.0
printfn "lingkaran r=7: %.2f" (luas (Lingkaran 7.0))
printfn "persegi s=4: %.2f" (luas (Persegi 4.0))
printfn "segitiga 6x3: %.2f" (luas (Segitiga (6.0, 3.0)))lingkaran r=7: 153.94
persegi s=4: 16.00
segitiga 6x3: 9.00Nilai dibangun dengan menyebut nama kasus beserta datanya, misalnya Lingkaran 7.0. Fungsi luas menerima Bentuk apa pun karena ketiga kasus sudah ditangani. Compiler menyimpulkan tipe parameternya dari daftar kasus, sehingga tidak ada anotasi tipe yang perlu ditulis secara manual.
Nama tipe sum berasal dari perbandingan dengan cara tipe lain menggabungkan data. Tuple dan record adalah tipe hasil kali (product type) karena isinya berupa seluruh field sekaligus, sedangkan DU adalah tipe hasil jumlah (sum type) karena nilainya berupa salah satu dari beberapa alternatif. Perbedaan itu menjelaskan mengapa satu record bentuk geometri memuat field yang tidak relevan pada sebagian keadaan, sedangkan DU tidak pernah membawa data milik kasus lain.
Konsumsi lewat pattern matching#
Ekspresi match adalah cara baku mengonsumsi nilai DU. Setiap cabang match menyebut satu kasus dan membongkar datanya menjadi nama, lalu badan cabang menghitung hasil untuk kasus tersebut. Nilai DU yang membawa data memaksa pembongkaran dilakukan sebelum data dapat digunakan, sehingga tidak ada akses data yang lepas dari cabangnya.
Berkas berikut memodelkan status satu buku pada perpustakaan sekolah. Kasus Tersedia dan Hilang tidak membawa data, sedangkan Dipinjam membawa nama peminjam dan tanggal pinjam.
// Status satu buku pada perpustakaan sekolah.
type StatusPeminjaman =
| Tersedia
| Dipinjam of peminjam: string * tanggal: string
| Hilang
let deskripsi status =
match status with
| Tersedia -> "Buku berada di rak."
| Dipinjam (nama, tanggal) -> sprintf "Dipinjam oleh %s sejak %s." nama tanggal
| Hilang -> "Buku dilaporkan hilang."
let bukuDiRak = Tersedia
let bukuDipinjam = Dipinjam ("Ali", "2026-03-02")
let bukuHilang = Hilang
printfn "%s" (deskripsi bukuDiRak)
printfn "%s" (deskripsi bukuDipinjam)
printfn "%s" (deskripsi bukuHilang)
// data kasus ikut tercetak ketika nilainya dicetak
printfn "%A" bukuDipinjamBuku berada di rak.
Dipinjam oleh Ali sejak 2026-03-02.
Buku dilaporkan hilang.
Dipinjam ("Ali", "2026-03-02")Model ini membuat keadaan tidak lengkap tidak dapat ditulis. Nilai dengan status dipinjam selalu membawa nama peminjam dan tanggalnya, karena kedua data itu adalah bagian dari kasus Dipinjam. Nilai DU juga memiliki kesetaraan struktural, dan keluaran %A memperlihatkan kasus beserta datanya ketika nilai dicetak.
Ekspresi match adalah ekspresi dalam arti penuh, sehingga seluruh cabangnya menghasilkan nilai dengan tipe yang sama dan hasilnya dapat langsung digunakan. Fungsi deskripsi pada berkas di atas tidak membutuhkan pernyataan tambahan karena setiap cabang sudah menghasilkan satu string. Satu evaluasi match hanya menjalankan satu cabang, yaitu cabang yang polanya cocok dengan nilai yang diberikan.
Kasus yang terlewat tetap terlihat#
Compiler menghitung seluruh kasus sebuah DU. Apabila ada kasus baru ditambahkan, setiap match yang belum menanganinya mendapat peringatan (warning), bukan error. Berkas berikut memodelkan suhu dengan tiga kasus, sedangkan fungsinya hanya menangani dua kasus.
// tidak-dijalankan: berkas ini sengaja memuat match yang tidak lengkap
type Suhu =
| Celsius of float
| Fahrenheit of float
| Kelvin of float
let keCelsius suhu =
match suhu with
| Celsius c -> c
| Fahrenheit f -> (f - 32.0) * 5.0 / 9.0
printfn "%.1f" (keCelsius (Celsius 25.0))Perintah berikut menjalankan berkas tersebut. Warning yang muncul memang diharapkan, dan bagian jalur folder pada pesan disingkirkan agar ringkas.
# match tidak lengkap hanya memicu warning, lalu program tetap berjalan
dotnet fsi kasus-terlewat.fsx 2>&1 | sed "s|.*/||"kasus-terlewat.fsx(8,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value 'Kelvin (_)' may indicate a case not covered by the pattern(s).
25.0Pesan warning FS0025 menunjuk baris match yang tidak lengkap dan bahkan memberi contoh kasus yang belum ditangani, yaitu Kelvin (_). Program tetap berjalan dan menghitung kasus yang sudah ditangani, sebagaimana terlihat dari keluaran 25.0. Menambahkan cabang untuk Kelvin, misalnya konversi dari skala Kelvin, menghilangkan warning tersebut sekaligus menuntaskan match.
Kebiasaan menambahkan cabang penangkap semua | _ -> menghilangkan warning, tetapi juga menghilangkan pengawasannya. Compiler tidak lagi memberi tahu kasus yang terlewat karena pola _ cocok dengan segalanya. Menuliskan setiap kasus secara eksplisit hanya memakan satu baris per kasus, sedangkan keuntungannya berupa daftar titik sunting yang diberikan compiler setiap kali domain berkembang.
Kapan discriminated union tepat digunakan#
DU paling tepat untuk data yang nilainya adalah alternatif eksklusif. Alternatif tersebut dapat dinyatakan juga dengan kombinasi bool dan string, tetapi bentuknya mengizinkan keadaan ilegal. Model Dipinjam: bool dengan field Oleh: string memungkinkan Dipinjam bernilai false sementara Oleh masih terisi, dan compiler tidak punya dasar untuk menolaknya.
DU menyelesaikan masalah itu dengan membuat setiap keadaan eksplisit, satu kasus satu bentuk data, sehingga keadaan campuran tidak dapat dikonstruksi. Ciri domain yang cocok dipetakan ke DU antara lain status peminjaman, skala suhu, bentuk geometri, dan tahapan sebuah proses. Sebaliknya, data yang seluruh fieldnya selalu relevan lebih pas dituliskan sebagai record, dan himpunan konstanta tanpa data lebih pas dituliskan sebagai enum yang dibahas pada topik tersendiri.
Pertumbuhan domain memperlihatkan keuntungan DU secara konkret. Apabila perpustakaan menambahkan status baru untuk buku yang direservasi, satu kasus cukup ditambahkan ke StatusPeminjaman, lalu compiler menunjuk setiap fungsi match yang belum menanganinya. Pada model berbasis bool dan string, status baru harus ditebak dari isi teks, dan setiap tempat yang membaca status harus disunting tanpa peta yang lengkap.
Ringkasan#
- DU mendeklarasikan sejumlah kasus, dan setiap kasus membawa bentuk datanya sendiri, sehingga alternatif domain dipetakan satu banding satu.
- Nilai DU dikonstruksi dengan menyebut nama kasus, sedangkan konsumsinya dilakukan lewat
matchyang membongkar data setiap kasus. - Compiler memberi peringatan FS0025 atas match yang tidak meliputi seluruh kasus, dan kasus yang terlewat saat berjalan melempar
MatchFailureException. - Keadaan tidak lengkap pada data domain tidak dapat ditulis karena datanya menjadi bagian dari kasusnya.
Referensi#
- Discriminated Unions — Microsoft Learn — Dokumentasi resmi DU F#, termasuk kasus berdata dan penggunaan pattern matching.
- Language reference F# — Microsoft Learn — Rujukan menyeluruh untuk sintaks bahasa F#.