Kegagalan pada program Haskell dinyatakan sebagai data, bukan sebagai exception yang dilempar keluar dari alur. Tipe Maybe menyatakan hasil yang bisa kosong, sedangkan tipe Either menyatakan hasil yang bisa membawa pesan error. Topik ini membahas keduanya sebagai tipe sum bawaan beserta pola penanganannya, mulai dari pencarian yang gagal hingga pembacaan angka dari teks. Setelah membacanya, fungsi yang bisa gagal dapat ditulis dengan tipe keluaran yang jujur, dan pemanggilnya memperoleh kewajiban untuk menangani semua kemungkinan.
Maybe untuk hasil yang bisa kosong#
Maybe adalah tipe sum bawaan pada library standar base dengan dua alternatif, yaitu Nothing untuk kekosongan dan Just x untuk hasil yang ada. Deklarasinya memuat satu parameter tipe, sehingga Maybe Int menyatakan hasil pencarian yang berupa angka atau kekosongan.
-- tidak-dijalankan
-- Tipe bawaan library standar base:
data Maybe a = Nothing | Just aBentuk data yang paling sering membutuhkan Maybe adalah pencarian pada pasangan kunci-nilai. Fungsi lookup bawaan bertipe Eq k => k -> [(k, v)] -> Maybe v, dan tipe kembalinya menyatakan kedua kemungkinan secara eksplisit. Dua evaluasi berikut memperlihatkan hasil pencarian yang berhasil dan yang gagal pada kumpulan pasangan yang sama.
# pencarian yang berhasil menghasilkan Just
ghc -e 'lookup 2 [(1, "satu"), (2, "dua")]'
# pencarian yang gagal menghasilkan Nothing
ghc -e 'lookup 7 [(1, "satu"), (2, "dua")]'Just "dua"
NothingTidak ada dua jalur keluaran yang berbeda di sini, karena pencarian yang berhasil dan yang gagal sama-sama menghasilkan nilai bertipe Maybe String. Pemanggil yang menerima hasilnya selalu tahu bahwa kekosongan itu mungkin terjadi, karena informasi tersebut tertulis pada tipe kembalinya, bukan pada dokumentasi terpisah.
Menggunakan nilai Maybe#
Fungsi yang membungkus pencarian dideklarasikan sebagaimana fungsi lain, yaitu memberi anotasi tipe berupa Maybe Int pada kembalian. Nilai Maybe kemudian digunakan melalui pencocokan pola atau melalui kombinator maybe yang menerima nilai cadangan, sebuah fungsi pengubah, dan nilai Maybe itu sendiri.
-- Maybe: kegagalan kosong tanpa exception
kamusHarga :: [(String, Int)]
kamusHarga = [("pensil", 3000), ("buku", 5000), ("penghapus", 2000)]
cariHarga :: String -> Maybe Int
cariHarga barang = lookup barang kamusHarga
main :: IO ()
main = do
print (cariHarga "buku")
print (cariHarga "penggaris")
print (maybe 0 id (cariHarga "buku"))
print (maybe 0 id (cariHarga "penggaris"))Just 5000
Nothing
5000
0Empat baris keluaran memperlihatkan dua tingkat penanganan, yaitu nilai mentah Maybe Int pada dua baris pertama dan nilai cadangan yang sudah dipilih pada dua baris terakhir. Ekspresi maybe 0 id menghasilkan isi Just apa adanya karena id digunakan sebagai pengubahnya, dan menghasilkan 0 apabila pencariannya gagal. Pola yang sama dapat ditulis dengan pencocokan pola, yaitu satu cabang Just harga yang menggunakan harga tersebut dan satu cabang Nothing yang menggunakan nilai cadangan. Bentuk pencocokan pola lebih panjang, tetapi memberi ruang untuk mengolah nilainya sebelum hasil dikembalikan, sehingga keduanya dipilih sesuai kebutuhan.
Either untuk error yang membawa pesan#
Maybe tidak menyimpan alasan kegagalannya, karena semua Nothing sama. Apabila penyebab kegagalan perlu disertakan, tipe Either a b lebih tepat, dengan alternatif Left a untuk jalur kegagalan yang membawa deskripsi dan alternatif Right b untuk hasil yang benar. Konvensi penamaannya mudah diingat karena “Right” juga berarti benar, sehingga jalur kanan digunakan untuk hasil yang diharapkan.
-- Either: kegagalan yang membawa pesan error
bacaAngka :: String -> Either String Int
bacaAngka teks = case reads teks of
[(n, "")] -> Right n
_ -> Left ("bukan bilangan bulat: " ++ teks)
bagiAman :: Int -> Int -> Either String Double
bagiAman _ 0 = Left "pembagian dengan nol"
bagiAman a b = Right (fromIntegral a / fromIntegral b)
main :: IO ()
main = do
print (bacaAngka "48")
print (bacaAngka "empat")
print (bagiAman 10 4)
print (bagiAman 10 0)Right 48
Left "bukan bilangan bulat: empat"
Right 2.5
Left "pembagian dengan nol"Fungsi bacaAngka menggunakan reads yang menghasilkan daftar hasil pembacaan, dan daftar yang kosong atau tak terduga diterjemahkan menjadi Left yang membawa teks aslinya. Fungsi bagiAman menunjukkan pola yang sama pada pembagian, yaitu pembagi nol menghasilkan Left dengan pesan yang menyebut penyebabnya. Keluaran pada baris kedua dan keempat memperlihatkan bahwa pesan kegagalan ikut terbawa sebagai data biasa yang dapat dicetak, disimpan, atau diteruskan.
Parameter Either tersusun dengan kegagalan di sebelah kiri, sehingga Either String Int terbaca sebagai hasil yang gagal dengan String atau berhasil dengan Int. Kedua fungsi pada program sepakat menggunakan String sebagai tipe kegagalannya, sehingga hasil keduanya bertipe sama dan dapat diperlakukan seragam. Susunan ini juga menampung kegagalan dari beberapa sumber pada satu tipe yang sama, dan pesan yang diperoleh tetap berupa data biasa yang dapat digabungkan sesuai kebutuhan program.
Menangani kedua tipe dengan konsisten#
Penanganan Either mengikuti pola penanganan Maybe, yaitu pencocokan pola pada kedua alternatif atau penggunaan kombinator. Kombinator either bertipe (a -> c) -> (b -> c) -> Either a b -> c, yaitu menerima satu fungsi untuk Left, satu fungsi untuk Right, dan mengembalikan hasil yang bertipe sama untuk kedua jalur. Dengan bentuk itu, kedua jalur kegagalan dan keberhasilan dipastikan bertemu pada satu tipe keluaran yang sama.
Tabel berikut merangkum kedua tipe tersebut secara berdampingan.
| Aspek | Maybe | Either |
|---|---|---|
| Alternatif kegagalan | Nothing | Left a |
| Alternatif keberhasilan | Just x | Right b |
| Pesan penyebab kegagalan | tidak disimpan | terbawa pada Left |
| Kombinator penanganannya | maybe | either |
Perbandingan tersebut memperlihatkan bahwa keduanya dibangun dari pola yang sama, yaitu dua alternatif yang menyertai hasil. Yang membedakan hanyalah kekayaan informasi pada jalur kegagalan, yaitu Nothing yang identik untuk semua kegagalan dan Left a yang dapat mengisi beragam deskripsi.
Pemilihan antara Maybe dan Either ditentukan oleh kebutuhan pesan, bukan oleh selera. Kasus kosong yang penyebabnya sudah jelas dari konteks cukup ditangani Maybe, sedangkan kegagalan yang penyebabnya perlu disertakan membutuhkan Either. Keduanya menutup celah yang pada bahasa lain ditempuh melalui exception, yaitu alur kegagalan yang tidak terlihat pada tipe fungsi. Pada Haskell, kegagalan menjadi bagian dari tipe, sehingga compiler ikut menjaga penanganannya.
Ringkasan#
Maybemenyatakan hasil yang bisa kosong dengan dua alternatif, yaituNothingdanJust x, sehingga kekosongan tertulis pada tipe kembalian.- Kombinator
maybememberi nilai cadangan tanpa pencocokan pola, sedangkan pencocokan pola menangani kedua alternatif secara eksplisit. Eithermenyatakan kegagalan yang membawa pesan melaluiLeftdan hasil melaluiRight.- Kegagalan yang dibawa sebagai data memaksa penanganan pada waktu kompilasi, berbeda dari exception yang menyembunyikan alur kegagalannya.
Referensi#
- Data.Maybe — Hackage
— Dokumentasi resmi tipe
Maybebeserta kombinatornya pada library standarbase. - Data.Either — Hackage
— Dokumentasi resmi tipe
Eitherbeserta fungsi-fungsi pendukungnya. - base package — Hackage
— Halaman utama library standar
baseyang menjadi rumah kedua tipe tersebut.