Topik ini membahas tipe Option, yaitu tipe yang menyatakan kehadiran atau ketidakhadiran nilai secara eksplisit pada tipe hasil sebuah fungsi. Pembahasan mencakup konstruktor Some dan None, fungsi-fungsi pada modul Option, larangan null yang diperiksa saat kompilasi, serta konversi dari null yang berasal dari batas .NET. Contoh yang digunakan berupa pencarian nilai ujian pada kamus sederhana.
Option sebagai Some atau None#
Tipe Option adalah tipe dengan dua kemungkinan, yaitu Some yang membawa satu nilai dan None yang menandakan ketidakhadiran nilai. Bentuknya menyerupai discriminated union dengan dua kasus, sehingga nilai yang mungkin absen tidak lagi dinyatakan dengan null, melainkan dengan tipe hasil yang jelas. Fungsi yang dapat gagal menemukan nilainya dideklarasikan mengembalikan int option atau string option, dan pemanggil menangani kedua kemungkinan tersebut.
Kamus nilai ujian berikut memperlihatkan pola itu. Fungsi pencarian mengembalikan int option karena nama yang dicari belum tentu terdaftar. Keluarannya menunjukkan Some untuk nama yang ada dan None untuk nama yang tidak ada, tanpa satu pun null pada kode.
// Kamus nilai ujian: ketidakhadiran nilai terlihat dari tipenya
let kamus = Map [ ("Ali", 85); ("Budi", 78); ("Citra", 92) ]
let cari (nama: string) : int option = Map.tryFind nama kamus
printfn "nilai Ali: %A" (cari "Ali")
printfn "nilai Dewi: %A" (cari "Dewi")
printfn "Dewi tidak terdaftar: %b" (Option.isNone (cari "Dewi"))
printfn "Ali terdaftar: %b" (Option.isSome (cari "Ali"))nilai Ali: Some 85
nilai Dewi: None
Dewi tidak terdaftar: true
Ali terdaftar: trueNilai Some 85 dan None bertipe sama, yaitu int option, sehingga keduanya dapat disimpan dalam variabel yang sama atau diteruskan ke fungsi yang sama. Format %A menampilkan konstruktor beserta isinya, sebagaimana discriminated union pada umumnya. Pola ini memindahkan pertanyaan tentang keberadaan nilai dari waktu eksekusi ke waktu kompilasi, karena compiler menolak penggunaan int option seolah-olah int biasa.
Secara definisi, Option sendiri adalah discriminated union dengan dua kasus yang didefinisikan oleh bahasa. Oleh karena itu, pencocokan pola | Some nilai -> ... | None -> ... tetap tersedia apabila transformasi bawaan belum mencukupi. Fungsi-fungsi pada modul Option pada dasarnya hanya membungkus pola tersebut agar alurnya lebih ringkas.
Fungsi pada modul Option#
Modul Option menyediakan fungsi-fungsi untuk mengolah nilai Option tanpa membongkarnya dengan match secara manual. Fungsi Option.map mengubah nilai di dalam Some dan membiarkan None lewat tanpa disentuh. Fungsi Option.bind menyambungkan Option ke fungsi berikutnya yang juga menghasilkan Option, sedangkan Option.defaultValue menyediakan nilai pengganti apabila hasilnya None. Pemeriksaan Option.isNone dan Option.isSome menguji keberadaan nilai tanpa mengambil isinya.
Berkas berikut merangkai dua pencarian beruntun, yaitu nama peserta menjadi nomor peserta, lalu nomor peserta menjadi status kelulusan. Setiap pencarian mengembalikan Option, dan Option.bind menyambungkannya sehingga kegagalan pada tahap mana pun langsung berakhir sebagai None.
// Transformasi Option dengan map, bind, dan defaultValue
let nomorPeserta = Map [ ("Ali", "A-01"); ("Budi", "A-02"); ("Citra", "A-03") ]
let nilaiUjian = Map [ ("A-01", 85); ("A-03", 92) ]
let status nama =
Map.tryFind nama nomorPeserta
|> Option.bind (fun nomor -> Map.tryFind nomor nilaiUjian)
|> Option.map (fun angka -> if angka >= 75 then "lulus" else "remedial")
|> Option.defaultValue "data tidak lengkap"
printfn "%s" (status "Ali")
printfn "%s" (status "Budi")
printfn "%s" (status "Dewi")
printfn "%b" (Option.isNone (Map.tryFind "Dewi" nomorPeserta))lulus
data tidak lengkap
data tidak lengkap
truePeserta Budi memiliki nomor peserta, tetapi nomor tersebut tidak ada pada kamus nilai, sehingga hasil akhirnya None dan Option.defaultValue menggantinya dengan teks pengganti. Kegagalan pada tahap pertama dan tahap kedua berakhir sama, sehingga pemanggil tidak perlu membedakan letak kegagalannya. Rangkaian seperti ini menggantikan pencabangan bersarang yang biasanya muncul ketika setiap tahap diperiksa satu per satu.
Modul Option juga menyediakan Option.get yang mengambil isi Some dan melempar exception apabila nilainya None. Fungsi ini hanya tepat pada keadaan ketika ketidakhadiran nilai sudah tidak mungkin terjadi, misalnya setelah pemeriksaan Option.isSome pada baris yang sama. Karena keamanannya bergantung pada asumsi itu, penggunaannya sebaiknya dibatasi.
null yang dilarang saat kompilasi#
Bahasa F# tidak memperlakukan null sebagai nilai yang sah untuk tipe-tipe milik F# seperti option, record, dan discriminated union. Penugasan null ke tipe tersebut ditolak compiler dengan error FS0043, yaitu pesan yang menyatakan bahwa tipe tersebut tidak memiliki null sebagai nilai yang sah.
// tidak-dijalankan: baris terakhir memicu error saat kompilasi
let nilai: int option = nullPesan errornya berbunyi The type 'int option' does not have 'null' as a proper value. Larangan itu berlaku pada tipe-tipe milik F#, sedangkan string, array, dan tipe lain dari pustaka .NET tetap dapat menampung null demi interoperabilitas. Akibatnya, kode F# murni praktis tidak memproduksi null, dan pertanyaan apakah sebuah nilai ada sudah terjawab oleh tipenya.
Konversi dari null di batas .NET#
Pustaka .NET yang ditulis dalam bahasa lain tetap dapat mengembalikan null dari fungsinya, misalnya metode pencarian yang memberikan null ketika data tidak ditemukan. Fungsi Option.ofObj membungkus nilai yang mungkin null menjadi Option, yaitu None apabila nilainya null dan Some apabila tidak. Fungsi kebalikannya, Option.toObj, mengubah Option kembali menjadi nilai yang boleh null ketika kode F# dipanggil dari arah .NET.
// Batas dengan pustaka .NET yang masih memakai null
let cariSiswa (nama: string) : string =
if nama = "Ali" then "Ali - kelas 3A" else null
// pustaka .NET mengembalikan null apabila datanya tidak ada
printfn "hasil mentah Dewi berupa null: %b" (isNull (cariSiswa "Dewi"))
// Option.ofObj membungkus nilai yang mungkin null menjadi Option
let alihAli = Option.ofObj (cariSiswa "Ali")
let alihDewi = Option.ofObj (cariSiswa "Dewi")
printfn "%A" alihAli
printfn "%A" alihDewi
// Option.toObj mengembalikan nilai ke dunia null saat keluar ke .NET
printfn "alihDewi kembali berupa null: %b" (isNull (Option.toObj alihDewi))hasil mentah Dewi berupa null: true
Some "Ali - kelas 3A"
None
alihDewi kembali berupa null: trueKonversi sebaiknya dilakukan sekali pada batas, yaitu saat data masuk dari pustaka .NET atau saat data keluar ke .NET. Bagian dalam kode F# menggunakan Option secara konsisten sehingga tidak ada pemeriksaan null yang tersebar. Dengan pola ini, seluruh kehati-hatian terhadap null berkumpul di satu tempat yang mudah diaudit.
Ringkasan#
- Option dinyatakan dengan dua kasus, yaitu
Someyang membawa nilai danNoneyang menandakan ketidakhadiran, sehingga nilai yang mungkin absen terlihat dari tipe hasilnya. - Option.map, Option.bind, dan Option.defaultValue mengolah nilai Option tanpa pencabangan manual, sedangkan Option.isNone dan Option.isSome mengujinya.
- null bukan nilai yang sah untuk tipe milik F#, dan compiler menolak penugasannya dengan error FS0043.
- Option.ofObj dan Option.toObj menjadi jembatan dua arah dengan pustaka .NET yang masih menggunakan null.
Referensi#
- Options — Microsoft Learn — Dokumentasi resmi tipe Option, modul Option, dan konversi nilainya.
- Language reference F# — Microsoft Learn — Rujukan menyeluruh untuk sintaks bahasa F#.