Mungkin pengecualian monad vs

Jawaban:

57

Menggunakan Maybe(atau sepupunya Eitheryang 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 indexOfakan mengembalikan Maybenilai karena Anda mengharapkan kemungkinan bahwa item tersebut tidak ada dalam daftar. Ini seperti kembali nulldari suatu fungsi, kecuali dengan cara yang aman yang memaksa Anda untuk menangani nullkasus ini. Eitherbekerja 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 Eitherdengan satu melempar pengecualian. Untuk kasus pengecualian, satu-satunya jalan nyata Anda adalah try...catchpernyataan. Untuk Eitherfungsinya, 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 Eitherini 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 optionalmembuat niat Anda lebih jelas daripada menggunakan try...catchuntuk selalu menangani pengecualian.

Perhatikan juga bahwa Anda tidak perlu membuang kode Anda dengan baris seperti if a == Nothing then Nothing else ...! Inti dari memperlakukan Maybedan Eithersebagai 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 Nothingdiberikan Nothing, dan itu pun mudah: ada banyak fungsi pustaka standar untuk membuat kode itu lebih bagus.

Akhirnya, keuntungan lain adalah a Maybe/ Eithertype 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 throwspernyataan) 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/ Eitheryang 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 Maybejuga Eithermengganti dicentang pengecualian. Anda akan menginginkan cara lain untuk menangani hal-hal seperti membaginya dengan 0 hanya karena akan menyebalkan jika setiap divisi mengembalikan Maybenilai.

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/ Eitherkarena saya merasa lebih sederhana dan lebih fleksibel.

Tikhon Jelvis
sumber
Sebagian besar setuju, tapi saya rasa contoh "opsional" tidak masuk akal - sepertinya Anda dapat melakukannya dengan pengecualian: void Opsional (tindakan) {coba {tindakan (); } catch (Exception) {}}
Errorsatz
11
  1. Pengecualian dapat membawa lebih banyak informasi tentang sumber masalah. OpenFile()dapat melempar FileNotFoundatau NoPermissionatau lainnya TooManyDescriptors. Tidak ada yang tidak membawa informasi ini.
  2. Pengecualian dapat digunakan dalam konteks yang tidak memiliki nilai balik (mis. Dengan konstruktor dalam bahasa yang memilikinya).
  3. Pengecualian memungkinkan Anda untuk dengan mudah mengirim informasi ke tumpukan, tanpa banyak if None return None pernyataan gaya.
  4. Penanganan pengecualian hampir selalu membawa dampak kinerja yang lebih tinggi daripada sekadar mengembalikan nilai.
  5. 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?"

    • Mungkin monad: "Dokter, tidak ada pasien di kamar 5."
    • Pengecualian: "Dokter, tidak ada kamar 5!"

    (perhatikan "jika" - ini berarti dokter mengharapkan Mungkin monad)

ek
sumber
7
Saya tidak setuju dengan poin 2 dan 3. Secara khusus, 2 sebenarnya tidak menimbulkan masalah dalam bahasa yang menggunakan monad karena bahasa-bahasa tersebut memiliki konvensi yang tidak menghasilkan teka-teki karena harus menangani kegagalan selama konstruksi. Mengenai 3, bahasa dengan monad memiliki sintaksis yang tepat untuk menangani monad secara transparan yang tidak memerlukan pemeriksaan eksplisit (misalnya, Nonenilai hanya dapat diperbanyak). Poin Anda 5 adalah jenis yang benar ... pertanyaannya adalah: situasi mana yang sangat luar biasa? Ternyata ... tidak banyak .
Konrad Rudolph
3
Mengenai (2), Anda dapat menulis bindsedemikian rupa sehingga pengujian untuk Nonetidak menimbulkan overhead sintaksis. Contoh yang sangat sederhana, C # hanya membebani Nullableoperator secara tepat. Tidak Noneperlu 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.
Konrad Rudolph
4
@ Oak: Mengenai (3), inti dari memperlakukan Maybesebagai monad adalah membuat propagasi Nonetersirat. Ini berarti bahwa jika Anda ingin mengembalikan yang Nonediberikan 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 Nonesemacam pernyataan.
Tikhon Jelvis
5
@ Oak: itu benar, tapi itu bukan maksud saya. Apa yang saya katakan adalah Anda mendapatkan nullpemeriksaan persis seperti itu (misalnya if Nothing then Nothing) secara gratis karena Maybemerupakan monad. Ini dikodekan dalam definisi bind ( >>=) untuk Maybe.
Tikhon Jelvis
2
Juga, mengenai (1): Anda dapat dengan mudah menulis monad yang dapat membawa informasi kesalahan (misalnya Either) yang berperilaku sama Maybe. Beralih di antara keduanya sebenarnya agak sederhana karena Maybesebenarnya hanya kasus khusus Either. (Dalam Haskell, Anda dapat menganggapnya Maybesebagai Either ().)
Tikhon Jelvis
6

"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.

Nemanja Trifunovic
sumber
2
Sebenarnya, saya memiliki sedikit kode yang melibatkan kamus di mana kunci yang hilang berarti hal-hal yang salah pada beberapa titik, kemungkinan besar bug dalam kode saya atau dalam kode yang mengubah input menjadi apa pun yang digunakan pada saat itu.
3
@ Darnan: Saya tidak mengatakan nilai yang hilang tidak pernah bisa menjadi tanda keadaan luar biasa - hanya saja tidak harus begitu. Mungkin benar-benar memodelkan situasi di mana variabel mungkin atau mungkin tidak memiliki nilai yang valid - pikirkan jenis nullable di C #.
Nemanja Trifunovic
6

Saya memberi jawaban kedua Tikhon, tetapi saya pikir ada poin praktis yang sangat penting yang hilang semua orang:

  1. Mekanisme penanganan pengecualian, setidaknya dalam bahasa umum, secara erat digabungkan dengan utas individu.
  2. The EitherMekanisme tidak digabungkan ke thread sama sekali.

Jadi sesuatu yang kita lihat saat ini dalam kehidupan nyata adalah bahwa banyak solusi pemrograman asinkron mengadopsi varian- Eithergaya 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:

  1. Merupakan hasil perhitungan asinkron, yang mungkin atau mungkin belum selesai;
  2. Memungkinkan Anda untuk membuat rantai operasi lebih lanjut untuk melakukan pada hasilnya, yang akan dipicu ketika hasil itu tersedia, dan yang hasilnya pada gilirannya tersedia sebagai janji;
  3. Memungkinkan Anda untuk menghubungkan penangan kegagalan yang akan dipanggil jika perhitungan janji gagal. Jika tidak ada penangan, maka kesalahan disebarkan ke penangan selanjutnya dalam rantai.

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.

sakundim
sumber
Tidak ada hubungannya dengan utas. JavaScript di browser selalu berjalan di satu utas bukan di utas ganda. Tetapi Anda masih tidak dapat menggunakan pengecualian karena Anda tidak tahu kapan fungsi Anda dipanggil di masa depan. Asynchronous tidak secara otomatis berarti keterlibatan utas. Itu juga alasan mengapa Anda tidak bisa bekerja dengan pengecualian. Anda hanya dapat mengambil pengecualian jika Anda memanggil suatu fungsi dan segera dieksekusi. Tetapi seluruh tujuan Asynchronous adalah bahwa ia berjalan di masa depan, seringkali ketika sesuatu yang lain selesai, dan tidak segera. Itu sebabnya Anda tidak bisa menggunakan pengecualian di sana.
David Raab
1

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.

chrisaycock
sumber
1
Ada pengecualian yang diperiksa di Jawa. Dan orang sering memperlakukan mereka dengan buruk.
Vladimir
1
@ DairT'arg ButJava membutuhkan pemeriksaan untuk kesalahan IO (sebagai contoh), tetapi tidak untuk NPE. Dalam Haskell itu sebaliknya: Yang setara dengan nol (Mungkin) dicentang tetapi kesalahan IO hanya membuang pengecualian (tidak dicentang). Saya tidak yakin berapa banyak perbedaan yang terjadi, tetapi saya tidak mendengar Haskellers mengeluh tentang Maybe dan saya pikir banyak orang ingin NPE diperiksa.
1
@delnan Orang ingin NPE diperiksa? Mereka pasti gila. Dan maksud saya itu. Membuat NPE dicentang hanya masuk akal jika Anda memiliki cara untuk menghindarinya di tempat pertama (dengan memiliki referensi yang tidak dapat dibatalkan, yang tidak dimiliki Java).
Konrad Rudolph
5
@KonradRudolph Ya, orang mungkin tidak ingin diperiksa dalam arti bahwa mereka harus menambahkan satu throws NPEke 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.
1
@ Dave Ah, kalau begitu aku setuju.
Konrad Rudolph
0

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.

Telastyn
sumber
8
Yah, itu tidak memiliki kelemahan yang sama karena dapat secara statis ketik diperiksa bila digunakan dengan benar. Tidak ada yang setara dengan pengecualian pointer nol saat menggunakan mungkin monads (sekali lagi, dengan asumsi mereka digunakan dengan benar).
Konrad Rudolph
Memang. Bagaimana menurut Anda yang harus diklarifikasi?
Telastyn
@ Konrad Itu karena untuk menggunakan nilai (mungkin) di Mungkin, Anda harus memeriksa Tidak ada (meskipun tentu saja ada kemampuan untuk mengotomatisasi banyak pemeriksaan itu). Bahasa lain menyimpan hal semacam itu manual (kebanyakan karena alasan filosofis saya percaya).
Donal Fellows
2
@Donal Ya, tetapi perhatikan bahwa sebagian besar bahasa yang menerapkan monad menyediakan gula sintaksis yang sesuai sehingga pemeriksaan ini seringkali dapat disembunyikan sepenuhnya. Misalnya, Anda bisa menambahkan dua Maybeangka dengan menulis a + b tanpa perlu memeriksa None, dan hasilnya adalah nilai opsional.
Konrad Rudolph
2
Perbandingan dengan tipe nullable berlaku untuk Maybe tipe tersebut , tetapi menggunakan Maybesebagai monad menambahkan gula sintaks yang memungkinkan pengekspresian logika null-ish jauh lebih elegan.
Pelaku
0

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.

rampok
sumber