Apakah masuk akal untuk memiliki konsep 'null' dan 'Maybe'?

11

Saat membuat klien untuk API web di C #, saya mengalami masalah terkait nulldengan nilai yang mewakili dua hal yang berbeda:

  • tidak ada , mis. foomungkin atau mungkin tidakbar
  • tidak diketahui : secara default respons API hanya mencakup subset properti, Anda harus menunjukkan properti tambahan yang Anda inginkan. Jadi tidak diketahui berarti bahwa properti itu tidak diminta dari API.

Setelah beberapa pencarian saya menemukan tentang jenis Maybe (atau Option), bagaimana ini digunakan dalam bahasa fungsional dan bagaimana "memecahkan" masalah null dereferencing dengan memaksa pengguna untuk berpikir tentang kemungkinan tidak adanya nilai. Namun, semua sumber yang saya temui berbicara tentang mengganti null dengan Maybe . Saya memang menemukan beberapa menyebutkan logika bernilai tiga , tapi saya tidak sepenuhnya memahaminya dan sebagian besar menyebutkannya dalam konteks "itu hal yang buruk".

Saya sekarang bertanya-tanya apakah masuk akal untuk memiliki konsep null dan Maybe , masing-masing untuk mewakili yang tidak diketahui dan tidak sama sekali . Apakah ini logika tiga nilai yang saya baca, atau apakah itu memiliki nama lain? Atau apakah cara yang dimaksudkan untuk bersarang dalam Maybe in a Maybe?

Stijn
sumber
9
Itu tidak membuat sanse memiliki null. Itu adalah ide yang benar-benar rusak.
Andrej Bauer
7
Jangan membuat mungkin-mungkin-foo yang memiliki semantik berbeda dari yang mungkin-foo. Mungkin adalah monad , dan salah satu undang-undang monad adalah M M xdan M xharus memiliki semantik yang sama.
Eric Lippert
2
Anda dapat mempertimbangkan melihat desain versi awal Visual Basic, yang tidak memiliki apa-apa (referensi ke objek tidak), Null (database nol semantik), Kosong (variabel tidak diinisialisasi), dan Hilang (parameter opsional tidak dilewatkan). Desain ini rumit dan dalam banyak hal tidak konsisten, tetapi ada alasan mengapa konsep-konsep itu tidak saling terkait.
Eric Lippert
3
Ya, saya juga tidak akan menggunakan Maybe Maybe. Tetapi, pada kenyataannya, Maybe asama dengan , secara semantik, sama dengan , dan isomorfik untuk mengetik dari jawaban @Andej. Anda dapat mendefinisikan turunan monad Anda sendiri untuk jenis ini juga dan karenanya menggunakan kombinator monad yang berbeda. a + 1 + 1a+1Maybe Maybe aa+1+1UserInput a
Euge
12
@EricLippert: salah bahwa " M (M x)dan M xharus memiliki semantik yang sama". Ambil M = Listcontoh: daftar daftar tidak sama dengan daftar. Ketika Madalah monad, ada transformasi (yaitu monad perkalian) dari M (M x)ke M xyang menjelaskan hubungan antara mereka, tetapi mereka tidak memiliki "semantik yang sama".
Andrej Bauer

Jawaban:

14

The nullnilai sebagai hadiah bawaan di mana-mana hanya ide yang sangat rusak , jadi lupa tentang itu.

Anda harus selalu memiliki konsep yang paling tepat menggambarkan data aktual Anda. Jika Anda memerlukan jenis yang menunjukkan "tidak dikenal", "tidak ada", dan "nilai", Anda harus memiliki itu. Tetapi jika itu tidak sesuai dengan kebutuhan Anda yang sebenarnya maka Anda seharusnya tidak melakukannya. Merupakan ide bagus untuk melihat apa yang digunakan orang lain dan apa yang mereka usulkan, tetapi Anda tidak harus mengikuti mereka secara membabi buta.

Orang-orang yang merancang perpustakaan standar mencoba menebak pola penggunaan umum, dan mereka biasanya melakukannya dengan baik, tetapi jika ada hal tertentu yang Anda butuhkan, Anda harus mendefinisikannya sendiri. Misalnya, di Haskell, Anda dapat mendefinisikan:

data UnknownNothing a =
     Unknown
   | Nothing
   | Value a

Bahkan mungkin Anda harus menggunakan sesuatu yang lebih deskriptif yang lebih mudah diingat:

data UserInput a =
     Unknown
   | NotGiven
   | Answer a

Anda dapat menentukan 10 jenis yang akan digunakan untuk skenario yang berbeda. Kekurangannya adalah Anda tidak akan memiliki fungsi perpustakaan yang sudah ada sebelumnya (seperti yang ada Maybe), tetapi ini biasanya menjadi detail. Tidak terlalu sulit untuk menambahkan fungsionalitas Anda sendiri.

Andrej Bauer
sumber
Masuk akal saat Anda melihatnya dijabarkan seperti itu. Saya sebagian besar tertarik pada ide, dukungan perpustakaan yang ada hanyalah bonus :)
Stijn
Andai saja penghalang untuk menciptakan tipe seperti itu lebih rendah dalam banyak bahasa. : /
Raphael
Kalau saja orang berhenti menggunakan bahasa dari tahun 1960-an (atau reinkarnasi mereka dari tahun 1980-an).
Andrej Bauer
@AndrejBauer: apa? tapi kemudian para ahli teori tidak punya apa pun untuk dikeluhkan!
Yttrill
Kami tidak mengeluh, kami tinggi-tinggi.
Andrej Bauer
4

Sejauh yang saya tahu, nilai nulldalam C # adalah nilai yang mungkin untuk beberapa variabel, tergantung pada jenisnya (apakah saya benar?). Misalnya, contoh beberapa kelas. Untuk jenis lainnya (seperti int,, booldll) Anda dapat menambahkan nilai pengecualian ini dengan mendeklarasikan variabel dengan int?atau bool?sebagai gantinya (ini persis seperti yang dilakukan oleh Maybekonstruktor, seperti yang akan saya jelaskan selanjutnya).

The Maybetipe konstruktor dari pemrograman fungsional menambahkan ini nilai pengecualian baru untuk datatype tertentu. Jadi jika Intadalah tipe bilangan bulat atau Gamejenis keadaan permainan, maka Maybe Intsemua bilangan bulat ditambah nilai nol (terkadang disebut tidak ada ). Sama untuk Maybe Game. Di sini, tidak ada tipe yang datang dengan nullnilai. Anda menambahkannya saat Anda membutuhkannya.

IMO, pendekatan terakhir ini adalah yang terbaik untuk bahasa pemrograman apa pun.

Euge
sumber
Ya, pemahaman Anda tentang C # benar. Jadi bisakah saya menyimpulkan dari jawaban Anda bahwa Anda menyarankan untuk bersarang Maybedan menjatuhkan nullseluruhnya?
Stijn
2
Pendapat saya adalah tentang bagaimana seharusnya sebuah bahasa. Saya tidak benar-benar tahu praktik terbaik pemrograman C #. Dalam kasus Anda, opsi terbaik adalah mendefinisikan tipe data seperti yang dijelaskan oleh @Andrej Bauer, tapi saya pikir Anda tidak bisa melakukannya di C #.
Euge
@Euge: Jenis-Jenis Jumlah Aljabar dapat (kurang-lebih) didekati dengan subtipe klasik OO-style dan pengiriman pesan polimorfik subtipe. Begitulah yang dilakukan di Scala, misalnya, dengan twist tambahan untuk memungkinkan tipe tertutup . Jenis menjadi superclass abstrak, konstruktor tipe menjadi subkelas beton, pencocokan pola di atas konstruktor dapat diperkirakan dengan memindahkan kasing ke dalam metode kelebihan beban pada subkelas beton atau isinstancepengujian. Scala juga memiliki pencocokan pola yang "tepat", dan C♯ sebenarnya baru saja mendapatkan pola yang sederhana juga.
Jörg W Mittag
"Perputaran tambahan" yang saya sebutkan untuk Scala adalah bahwa di samping "standar" pengubah "virtual" (untuk kelas yang dapat diwarisi dari) dan final(untuk kelas yang tidak dapat diwarisi dari), ia juga memiliki sealed, untuk kelas yang dapat diperluas hanya dalam unit kompilasi yang sama . Ini berarti bahwa kompiler dapat secara statis mengetahui semua subkelas yang mungkin (asalkan semuanya adalah diri mereka sendiri sealedatau final) dan dengan demikian melakukan pemeriksaan kelengkapan untuk kecocokan pola, yang tidak mungkin secara statis untuk bahasa dengan pemuatan kode runtime dan pewarisan yang tidak terbatas.
Jörg W Mittag
Tentu saja Anda dapat mendesain tipe dengan semantik tersebut dalam C #. Perhatikan bahwa C # tidak memungkinkan tipe bawaan bawaan (disebut Nullable dalam C #) bersarang. int??ilegal di C #.
Eric Lippert
3

Jika Anda dapat mendefinisikan serikat jenis, seperti X or Y, dan jika Anda memiliki jenis bernama Nullyang hanya mewakili nullnilai (dan tidak ada yang lain), maka untuk jenis apa pun T, T or Nullsebenarnya tidak jauh berbeda Maybe T. Satu-satunya masalah dengan nulladalah ketika memperlakukan bahasa tipe Tsebagai T or Nullimplisit, membuat nullnilai yang valid untuk setiap jenis (kecuali tipe primitif dalam bahasa yang memiliki mereka). Inilah yang terjadi misalnya di Jawa di mana fungsi yang mengambil Stringjuga menerima null.

Jika Anda memperlakukan tipe sebagai set nilai dan orsebagai gabungan set, maka (T or Null) or Nullmewakili set nilai T ∪ {null} ∪ {null}, yang sama dengan T or Null. Ada kompiler yang melakukan analisis semacam ini (SBCL). Seperti yang dikatakan dalam komentar, Anda tidak dapat dengan mudah membedakan antara Maybe Tdan Maybe Maybe Tketika Anda melihat jenis sebagai domain (di sisi lain, tampilan itu cukup berguna untuk program yang diketik secara dinamis). Tetapi Anda juga bisa menjaga tipe sebagai ekspresi aljabar, di mana (T or Null) or Nullmewakili beberapa level Maybes.

coredump
sumber
2
Sebenarnya, penting agar Maybe (Maybe X)tidak sama dengan Maybe X. Nothingtidak sama dengan Just Nothing. Berkembang Maybe (Maybe X)dengan Maybe Xmembuat tidak mungkin untuk mengobati secara Maybe _polimorfik.
Gilles 'SANGAT berhenti menjadi jahat'
1

Anda perlu mencari tahu apa yang ingin Anda ungkapkan, dan kemudian menemukan cara bagaimana mengekspresikannya.

Pertama, Anda memiliki nilai dalam domain masalah Anda (seperti angka, string, catatan pelanggan, boolean, dll). Kedua, Anda memiliki beberapa nilai generik tambahan di atas nilai domain masalah Anda: Misalnya "tidak ada" (ketiadaan nilai yang diketahui), "tidak diketahui" (tidak ada pengetahuan tentang ada atau tidaknya suatu nilai), tidak disebutkan adalah " sesuatu "(pasti ada nilainya, tapi kami tidak tahu yang mana). Mungkin Anda bisa memikirkan situasi lain.

Jika Anda ingin dapat mewakili semua nilai plus tiga nilai tambahan ini, Anda akan membuat enum dengan kasus "tidak ada", "tidak diketahui", "sesuatu", dan "nilai" dan pergi dari sana.

Dalam beberapa bahasa beberapa kasus lebih sederhana. Sebagai contoh, di Swift Anda memiliki untuk setiap jenis T jenis "opsional T" yang memiliki nilai yang mungkin nihil ditambah semua nilai T. Ini memungkinkan Anda menangani banyak situasi dengan mudah, dan sangat cocok jika Anda memiliki "tidak ada" dan " nilai". Anda dapat menggunakannya jika Anda memiliki "tidak dikenal" dan "nilai". Anda tidak dapat menggunakannya jika Anda perlu menangani "tidak ada", "tidak diketahui" dan "nilai". Bahasa lain mungkin menggunakan "null" atau "mungkin", tapi itu hanya kata lain.

Di JSON, Anda memiliki kamus dengan pasangan kunci / nilai. Di sini, sebuah kunci mungkin saja hilang dari kamus, atau mungkin memiliki nilai nol. Jadi dengan menjelaskan dengan cermat bagaimana barang-barang disimpan, Anda dapat mewakili nilai-nilai umum ditambah dua nilai tambahan.

gnasher729
sumber