Jawaban:
Menggunakan Maybe
(atau sepupunya Either
yang bekerja pada dasarnya dengan cara yang sama tetapi memungkinkan Anda mengembalikan nilai sewenang-wenang di tempat Nothing
) melayani tujuan yang sedikit berbeda dari pengecualian. Dalam istilah Java, ini seperti memiliki pengecualian yang diperiksa daripada pengecualian runtime. Ini mewakili sesuatu yang diharapkan yang harus Anda hadapi, bukan kesalahan yang tidak Anda harapkan.
Jadi fungsi seperti indexOf
akan mengembalikan Maybe
nilai karena Anda mengharapkan kemungkinan bahwa item tersebut tidak ada dalam daftar. Ini seperti kembali null
dari suatu fungsi, kecuali dengan cara yang aman yang memaksa Anda untuk menangani null
kasus ini. Either
bekerja dengan cara yang sama kecuali Anda dapat mengembalikan informasi yang terkait dengan kasus kesalahan, jadi sebenarnya lebih mirip dengan pengecualian daripada Maybe
.
Jadi apa keuntungan dari pendekatan Maybe
/ Either
? Untuk satu, itu adalah warga negara kelas bahasa. Mari kita bandingkan fungsi yang digunakan Either
dengan satu melempar pengecualian. Untuk kasus pengecualian, satu-satunya jalan nyata Anda adalah try...catch
pernyataan. Untuk Either
fungsinya, Anda bisa menggunakan kombinator yang ada untuk membuat kontrol aliran menjadi lebih jelas. Berikut adalah beberapa contoh:
Pertama, katakanlah Anda ingin mencoba beberapa fungsi yang bisa salah dalam satu baris sampai Anda mendapatkan yang tidak. Jika Anda tidak mendapatkan apa pun tanpa kesalahan, Anda ingin mengembalikan pesan kesalahan khusus. Ini sebenarnya adalah pola yang sangat berguna tetapi akan menggunakan rasa sakit yang mengerikan try...catch
. Untungnya, karena Either
ini hanya nilai normal, Anda dapat menggunakan fungsi yang ada untuk membuat kode lebih jelas:
firstThing <|> secondThing <|> throwError (SomeError "error message")
Contoh lain adalah memiliki fungsi opsional. Katakanlah Anda memiliki beberapa fungsi untuk dijalankan, termasuk yang mencoba mengoptimalkan kueri. Jika gagal, Anda ingin semuanya dijalankan. Anda dapat menulis kode seperti:
do a <- getA
b <- getB
optional (optimize query)
execute query a b
Kedua kasus ini lebih jelas dan lebih pendek daripada menggunakan try..catch
, dan, yang lebih penting, lebih semantik. Menggunakan fungsi seperti <|>
atau optional
membuat niat Anda lebih jelas daripada menggunakan try...catch
untuk selalu menangani pengecualian.
Perhatikan juga bahwa Anda tidak perlu membuang kode Anda dengan baris seperti if a == Nothing then Nothing else ...
! Inti dari memperlakukan Maybe
dan Either
sebagai seorang monad adalah untuk menghindari hal ini. Anda dapat menyandikan semantik propagasi ke dalam fungsi bind sehingga Anda mendapatkan pemeriksaan null / error secara gratis. Satu-satunya waktu Anda harus memeriksa secara eksplisit adalah jika Anda ingin mengembalikan sesuatu selain dari yang Nothing
diberikan Nothing
, dan itu pun mudah: ada banyak fungsi pustaka standar untuk membuat kode itu lebih bagus.
Akhirnya, keuntungan lain adalah a Maybe
/ Either
type lebih sederhana. Tidak perlu memperluas bahasa dengan kata kunci tambahan atau struktur kontrol - semuanya hanya perpustakaan. Karena mereka hanya nilai normal, itu membuat sistem jenis lebih sederhana - di Jawa, Anda harus membedakan antara jenis (misalnya jenis kembali) dan efek (misalnya throws
pernyataan) di mana Anda tidak akan menggunakan Maybe
. Mereka juga berperilaku seperti jenis yang ditentukan pengguna lainnya - tidak perlu kode penanganan kesalahan khusus dimasukkan ke dalam bahasa.
Kemenangan lain adalah bahwa Maybe
/ Either
yang functors dan monad, yang berarti mereka dapat mengambil keuntungan dari fungsi yang ada kontrol monad aliran (yang ada adalah nomor yang adil) dan, secara umum, bermain baik bersama dengan monads lainnya.
Yang mengatakan, ada beberapa peringatan. Untuk satu, tidak Maybe
juga Either
mengganti dicentang pengecualian. Anda akan menginginkan cara lain untuk menangani hal-hal seperti membaginya dengan 0 hanya karena akan menyebalkan jika setiap divisi mengembalikan Maybe
nilai.
Masalah lain adalah memiliki beberapa jenis kesalahan kembali (ini hanya berlaku untuk Either
). Dengan pengecualian, Anda dapat melemparkan berbagai jenis pengecualian dalam fungsi yang sama. dengan Either
, Anda hanya mendapatkan satu jenis. Ini dapat diatasi dengan sub-pengetikan atau ADT yang mengandung semua jenis kesalahan yang berbeda sebagai konstruktor (pendekatan kedua ini adalah apa yang biasanya digunakan dalam Haskell).
Namun, secara keseluruhan, saya lebih suka pendekatan Maybe
/ Either
karena saya merasa lebih sederhana dan lebih fleksibel.
OpenFile()
dapat melempar FileNotFound
atau NoPermission
atau lainnya TooManyDescriptors
. Tidak ada yang tidak membawa informasi ini.if None return None
pernyataan gaya.Yang paling penting, pengecualian dan monad Mungkin memiliki tujuan yang berbeda - pengecualian digunakan untuk menandakan masalah, sedangkan Mungkin tidak.
"Perawat, jika ada pasien di kamar 5, bisakah kamu memintanya menunggu?"
(perhatikan "jika" - ini berarti dokter mengharapkan Mungkin monad)
None
nilai hanya dapat diperbanyak). Poin Anda 5 adalah jenis yang benar ... pertanyaannya adalah: situasi mana yang sangat luar biasa? Ternyata ... tidak banyak .
bind
sedemikian rupa sehingga pengujian untuk None
tidak menimbulkan overhead sintaksis. Contoh yang sangat sederhana, C # hanya membebani Nullable
operator secara tepat. Tidak None
perlu memeriksa , bahkan saat menggunakan tipe. Tentu saja pemeriksaannya masih dilakukan (ini tipenya aman), tetapi di balik layar dan tidak mengacaukan kode Anda. Hal yang sama berlaku dalam beberapa hal untuk keberatan Anda terhadap keberatan saya untuk (5) tetapi saya setuju bahwa itu mungkin tidak selalu berlaku.
Maybe
sebagai monad adalah membuat propagasi None
tersirat. Ini berarti bahwa jika Anda ingin mengembalikan yang None
diberikan None
, Anda tidak perlu menulis kode khusus sama sekali. Satu-satunya waktu yang Anda butuhkan untuk mencocokkan adalah jika Anda ingin melakukan sesuatu yang istimewa None
. Anda tidak pernah membutuhkan if None then None
semacam pernyataan.
null
pemeriksaan persis seperti itu (misalnya if Nothing then Nothing
) secara gratis karena Maybe
merupakan monad. Ini dikodekan dalam definisi bind ( >>=
) untuk Maybe
.
Either
) yang berperilaku sama Maybe
. Beralih di antara keduanya sebenarnya agak sederhana karena Maybe
sebenarnya hanya kasus khusus Either
. (Dalam Haskell, Anda dapat menganggapnya Maybe
sebagai Either ()
.)
"Mungkin" bukan pengganti untuk pengecualian. Pengecualian dimaksudkan untuk digunakan dalam kasus luar biasa (misalnya: membuka koneksi db dan server db tidak ada meskipun seharusnya). "Mungkin" untuk memodelkan situasi ketika Anda mungkin atau mungkin tidak memiliki nilai yang valid; katakanlah Anda mendapatkan nilai dari kamus untuk sebuah kunci: mungkin ada atau mungkin tidak ada - tidak ada yang "luar biasa" tentang salah satu dari hasil ini.
Saya memberi jawaban kedua Tikhon, tetapi saya pikir ada poin praktis yang sangat penting yang hilang semua orang:
Either
Mekanisme tidak digabungkan ke thread sama sekali.Jadi sesuatu yang kita lihat saat ini dalam kehidupan nyata adalah bahwa banyak solusi pemrograman asinkron mengadopsi varian- Either
gaya penanganan kesalahan. Pertimbangkan janji Javascript , sebagaimana dirinci dalam salah satu tautan ini:
Konsep janji memungkinkan Anda menulis kode asinkron seperti ini (diambil dari tautan terakhir):
var greetingPromise = sayHello();
greetingPromise
.then(addExclamation)
.then(function (greeting) {
console.log(greeting); // 'hello world!!!!’
}, function(error) {
console.error('uh oh: ', error); // 'uh oh: something bad happened’
});
Pada dasarnya, janji adalah objek yang:
Pada dasarnya, karena dukungan pengecualian bahasa asli tidak berfungsi saat perhitungan Anda terjadi di beberapa utas, implementasi janji harus menyediakan mekanisme penanganan kesalahan, dan ini berubah menjadi monad yang mirip dengan tipe Maybe
/ Haskell Either
.
Sistem tipe Haskell akan mengharuskan pengguna untuk mengakui kemungkinan a Nothing
, sedangkan bahasa pemrograman sering tidak mengharuskan pengecualian ditangkap. Itu berarti bahwa kita akan tahu, pada waktu kompilasi, bahwa pengguna telah memeriksa kesalahan.
throws NPE
ke setiap tanda tangan tunggal dan catch(...) {throw ...}
ke setiap badan metode tunggal. Tapi saya percaya ada pasar untuk diperiksa dalam arti yang sama dengan Maybe: nullability adalah opsional dan dilacak dalam sistem tipe.
Monad mungkin pada dasarnya sama dengan sebagian besar bahasa mainstream yang menggunakan pemeriksaan "null means error" (kecuali itu membutuhkan nol untuk diperiksa), dan sebagian besar memiliki kelebihan dan kekurangan yang sama.
Maybe
angka dengan menulis a + b
tanpa perlu memeriksa None
, dan hasilnya adalah nilai opsional.
Maybe
tipe tersebut , tetapi menggunakan Maybe
sebagai monad menambahkan gula sintaks yang memungkinkan pengekspresian logika null-ish jauh lebih elegan.
Penanganan pengecualian bisa sangat menyebalkan untuk anjak piutang dan pengujian. Saya tahu python menyediakan sintaksis "dengan" yang bagus yang memungkinkan Anda menjebak pengecualian tanpa blok "coba ... tangkap" yang kaku. Namun di Jawa, misalnya, coba tangkap balok yang besar, boilerplate, baik verbose atau sangat verbose, dan sulit dipecah. Selain itu, Java menambahkan semua kebisingan di sekitar pengecualian diperiksa dan tidak dicentang.
Jika, sebaliknya, monad Anda menangkap pengecualian dan memperlakukannya sebagai properti ruang monadik (alih-alih beberapa anomali pemrosesan), maka Anda bebas untuk mencampur dan mencocokkan fungsi yang Anda ikat ke ruang tersebut terlepas dari apa yang mereka lempar atau tangkap.
Jika, lebih baik, monad Anda mencegah kondisi di mana pengecualian dapat terjadi (seperti mendorong cek nol ke Mungkin), maka bahkan lebih baik. jika ... maka jauh, lebih mudah untuk faktor dan tes daripada mencoba ... menangkap.
Dari apa yang saya lihat Go mengambil pendekatan yang sama dengan menetapkan bahwa setiap fungsi kembali (jawaban, kesalahan). Itu sama dengan "mengangkat" fungsi ke ruang monad di mana tipe jawaban inti didekorasi dengan indikasi kesalahan, dan secara efektif melompat-lompat & menangkap perkecualian.