Saya telah membaca tentang kenyamanan (bukan) memiliki null
alih-alih (misalnya) Maybe
. Setelah membaca artikel ini , saya yakin akan jauh lebih baik menggunakanMaybe
(atau yang serupa). Namun, saya terkejut melihat bahwa semua "terkenal" imperatif atau bahasa pemrograman berorientasi objek masih menggunakan null
(yang memungkinkan akses tidak dicentang ke jenis yang dapat mewakili nilai 'tidak ada'), dan yang Maybe
sebagian besar digunakan dalam bahasa pemrograman fungsional.
Sebagai contoh, lihat kode C # berikut:
void doSomething(string username)
{
// Check that username is not null
// Do something
}
Ada yang baunya tidak enak di sini ... Mengapa kita harus memeriksa jika argumennya nol? Bukankah kita seharusnya mengasumsikan bahwa setiap variabel berisi referensi ke suatu objek? Seperti yang Anda lihat, masalahnya adalah bahwa menurut definisi, hampir semua variabel dapat berisi referensi nol. Bagaimana jika kita dapat memutuskan variabel mana yang "nullable" dan mana yang tidak? Itu akan menyelamatkan kita dari banyak upaya saat debugging dan mencari "NullReferenceException". Bayangkan, secara default, tidak ada tipe yang bisa berisi referensi nol . Alih-alih itu, Anda akan menyatakan secara eksplisit bahwa variabel dapat berisi referensi nol , hanya jika Anda benar-benar membutuhkannya. Itulah ide di balik Maybe. Jika Anda memiliki fungsi yang dalam beberapa kasus gagal (misalnya pembagian dengan nol), Anda dapat mengembalikan aMaybe<int>
, menyatakan secara eksplisit bahwa hasilnya mungkin int, tetapi juga bukan apa-apa! Ini adalah salah satu alasan untuk memilih Mungkin daripada nol. Jika Anda tertarik pada lebih banyak contoh, maka saya sarankan untuk membaca artikel ini .
Faktanya adalah bahwa, meskipun ada kelemahan membuat sebagian besar tipe dapat dibatalkan secara default, sebagian besar bahasa pemrograman OO benar-benar melakukannya. Itu sebabnya saya bertanya-tanya tentang:
- Argumen seperti apa yang harus Anda terapkan
null
dalam bahasa pemrograman Anda, bukanMaybe
? Apakah ada alasan atau hanya "bagasi bersejarah"?
Harap pastikan Anda memahami perbedaan antara nol dan Mungkin sebelum menjawab pertanyaan ini.
null
atau konsep tidak ada (IIRC Haskell adalah salah satu contohnya).null
di dalamnya. Tidak mudah menjatuhkannya begitu saja.Jawaban:
Saya percaya ini adalah bawaan sejarah.
Bahasa yang paling menonjol dan tertua
null
adalah C dan C ++. Tapi di sini,null
memang masuk akal. Pointer masih cukup numerik dan konsep tingkat rendah. Dan bagaimana orang lain berkata, dalam pola pikir programmer C dan C ++, harus secara eksplisit mengatakan bahwa pointernull
itu tidak masuk akal.Baris kedua adalah Java. Mempertimbangkan pengembang Java sedang berusaha mendekati C ++, sehingga mereka dapat membuat transisi dari C ++ ke Java lebih sederhana, mereka mungkin tidak ingin mengacaukan konsep inti bahasa tersebut. Selain itu, penerapan eksplisit
null
akan membutuhkan lebih banyak upaya, karena Anda harus memeriksa apakah referensi non-nol benar-benar ditetapkan dengan benar setelah inisialisasi.Semua bahasa lain sama dengan Java. Mereka biasanya menyalin cara C ++ atau Java melakukannya, dan mempertimbangkan bagaimana konsep inti
null
dari jenis referensi implisit , menjadi sangat sulit untuk merancang bahasa yang menggunakan eksplisitnull
.sumber
null
tidak akan menjadi perubahan besar, saya pikir.std::string
tidak mungkinnull
.int&
tidak mungkinnull
. Sebuahint*
kaleng, dan C ++ memungkinkan akses yang tidak dicentang untuk itu karena dua alasan: 1. karena C lakukan dan 2. karena Anda seharusnya memahami apa yang Anda lakukan ketika menggunakan pointer mentah di C ++.Sebenarnya,
null
itu ide bagus. Diberikan pointer, kami ingin menunjukkan bahwa pointer ini tidak mereferensikan nilai yang valid. Jadi kami mengambil satu lokasi memori, menyatakan itu tidak valid, dan tetap pada konvensi itu (konvensi yang kadang-kadang diberlakukan dengan segfault). Sekarang setiap kali saya memiliki pointer saya dapat memeriksa apakah itu berisiNothing
(ptr == null
) atauSome(value)
(ptr != null
,value = *ptr
). Saya ingin Anda mengerti bahwa ini setara denganMaybe
tipe.Masalah dengan ini adalah:
Dalam banyak bahasa, sistem tipe tidak membantu di sini untuk menjamin referensi yang tidak nol.
Ini adalah muatan historis, karena banyak bahasa arus utama imperatif atau OOP hanya memiliki kemajuan tambahan dalam sistem tipenya jika dibandingkan dengan pendahulunya. Perubahan kecil memiliki keuntungan bahwa bahasa baru lebih mudah dipelajari. C # adalah bahasa utama yang telah memperkenalkan alat tingkat bahasa untuk menangani nulls dengan lebih baik.
Desainer API mungkin kembali
null
pada kegagalan, tetapi tidak merujuk pada hal yang sebenarnya pada kesuksesan. Seringkali, benda (tanpa referensi) dikembalikan secara langsung. Perataan satu tingkat penunjuk ini memungkinkan untuk digunakannull
sebagai nilai.Ini hanya kemalasan di sisi desainer dan tidak dapat membantu tanpa menegakkan sarang yang tepat dengan sistem tipe yang tepat. Beberapa orang mungkin juga mencoba untuk membenarkan ini dengan pertimbangan kinerja, atau dengan adanya pemeriksaan opsional (koleksi mungkin kembali
null
atau barang itu sendiri, tetapi juga menyediakancontains
metode).Di Haskell ada pandangan rapi ke
Maybe
tipe sebagai monad. Ini membuatnya lebih mudah untuk menyusun transformasi pada nilai yang terkandung.Di sisi lain, bahasa tingkat rendah seperti C nyaris tidak memperlakukan array sebagai tipe terpisah, jadi saya tidak yakin apa yang kami harapkan. Dalam bahasa OOP dengan polimorfisme parameter,
Maybe
tipe yang diperiksa runtime agak sepele untuk diterapkan.sumber
null
kurang dari yang mereka lakukan di masa lalu?null
s ” - Saya berbicara tentangNullable
jenis dan??
operator yang default. Itu tidak memecahkan masalah saat ini di hadapan kode warisan, tetapi merupakan langkah menuju masa depan yang lebih baik.Nullable
.Pemahaman saya adalah
null
konstruksi yang diperlukan untuk abstrak bahasa pemrograman di luar perakitan. 1 Programmer membutuhkan kemampuan untuk menunjukkan bahwa pointer atau nilai register adalahnot a valid value
dannull
menjadi istilah umum untuk makna itu.Memperkuat poin yang
null
hanya konvensi untuk mewakili konsep, nilai aktual untuknull
dapat / dapat bervariasi berdasarkan bahasa dan platform pemrograman.Jika Anda merancang bahasa baru dan ingin menghindari
null
tetapi menggunakanmaybe
sebagai gantinya, maka saya akan mendorong istilah yang lebih deskriptif sepertinot a valid value
ataunavv
untuk menunjukkan maksudnya. Tetapi nama yang tidak bernilai itu adalah konsep yang terpisah dari apakah Anda harus membiarkan nilai yang tidak ada itu ada dalam bahasa Anda.Sebelum Anda dapat memutuskan salah satu dari dua poin tersebut, Anda perlu menentukan apa arti dari
maybe
arti bagi sistem Anda. Anda mungkin menemukan itu hanya mengubah namanull
maknanot a valid value
atau Anda mungkin menemukan itu memiliki semantik yang berbeda untuk bahasa Anda.Demikian juga, keputusan apakah akan memeriksa akses terhadap
null
akses atau referensi adalah keputusan desain lain dari bahasa Anda.Untuk memberikan sedikit sejarah,
C
memiliki asumsi implisit bahwa programmer memahami apa yang mereka coba lakukan ketika memanipulasi memori. Karena itu adalah abstraksi yang unggul untuk perakitan dan bahasa-bahasa penting yang mendahuluinya, saya berani mengatakan bahwa pemikiran untuk melindungi para programmer dari referensi yang salah tidak muncul di benak mereka.Saya percaya bahwa beberapa kompiler atau alat tambahan mereka dapat memberikan ukuran pemeriksaan terhadap akses pointer yang tidak valid. Jadi orang lain telah mencatat masalah potensial ini dan mengambil langkah-langkah untuk melindunginya.
Apakah Anda mengizinkan atau tidak tergantung pada apa yang Anda ingin capai dalam bahasa Anda dan tingkat tanggung jawab apa yang ingin Anda sampaikan kepada pengguna bahasa Anda. Ini juga tergantung pada kemampuan Anda membuat kompiler untuk membatasi jenis perilaku itu.
Jadi untuk menjawab pertanyaan Anda:
"Argumen seperti apa ..." - Yah, itu tergantung pada apa yang Anda ingin bahasa itu lakukan. Jika Anda ingin mensimulasikan akses bare-metal maka Anda mungkin ingin mengizinkannya.
"Apakah ini hanya bagasi bersejarah?" Mungkin, mungkin juga tidak.
null
tentu memiliki / memiliki arti untuk sejumlah bahasa dan membantu mengarahkan ekspresi bahasa-bahasa tersebut. Preseden historis mungkin telah memengaruhi bahasa yang lebih baru dan memungkinkannyanull
tetapi agak berlebihan untuk melambaikan tangan Anda dan mendeklarasikan konsep bagasi historis yang tidak berguna.1 Lihat artikel Wikipedia ini meskipun kredit diberikan untuk Hoare untuk nilai-nilai nol dan bahasa berorientasi objek. Saya percaya bahasa imperatif berkembang di sepanjang silsilah keluarga yang berbeda dari Algol.
sumber
null
).object o = null; o.ToString();
mengkompilasi dengan baik bagi saya, tanpa kesalahan atau peringatan di VS2012. ReSharper mengeluhkan hal itu, tapi itu bukan kompiler.Jika Anda melihat contoh di artikel yang Anda kutip, sebagian besar waktu menggunakan
Maybe
tidak mempersingkat kode. Itu tidak meniadakan kebutuhan untuk memeriksaNothing
. Satu-satunya perbedaan adalah itu mengingatkan Anda untuk melakukannya melalui sistem tipe.Catatan, saya katakan "ingatkan," bukan paksaan. Pemrogram malas. Jika seorang programmer yakin suatu nilai tidak mungkin
Nothing
, mereka akan melakukan dereferensiMaybe
tanpa memeriksanya, seperti halnya mereka men-decereer null pointer sekarang. Hasil akhirnya adalah Anda mengubah pengecualian penunjuk nol menjadi pengecualian "mungkin kosong".Prinsip yang sama dari sifat manusia berlaku di bidang lain di mana bahasa pemrograman mencoba memaksa pemrogram untuk melakukan sesuatu. Sebagai contoh, perancang Java mencoba memaksa orang untuk menangani sebagian besar pengecualian, yang menghasilkan banyak pelat baja yang mengabaikan secara diam-diam atau menyebarkan pengecualian secara membuta.
Apa yang membuat
Maybe
itu menyenangkan adalah ketika banyak keputusan dibuat melalui pencocokan pola dan polimorfisme alih-alih pemeriksaan eksplisit. Misalnya, Anda dapat membuat fungsi terpisahprocessData(Some<T>)
danprocessData(Nothing<T>)
yang tidak dapat Anda lakukannull
. Anda secara otomatis memindahkan penanganan kesalahan Anda ke fungsi yang terpisah, yang sangat diinginkan dalam pemrograman fungsional di mana fungsi diedarkan dan dievaluasi dengan malas daripada selalu dipanggil secara top-down. Dalam OOP, cara yang lebih disukai untuk memisahkan kode penanganan kesalahan Anda adalah dengan pengecualian.sumber
const
, tetapi menjadikannya opsional. Beberapa jenis kode tingkat rendah, seperti daftar yang ditautkan misalnya, akan sangat menjengkelkan untuk diterapkan dengan objek yang tidak dapat dibatalkan.map :: Maybe a -> (a -> b)
danbind :: Maybe a -> (a -> Maybe b)
didefinisikan di atasnya, sehingga Anda dapat melanjutkan dan utas perhitungan lebih lanjut untuk sebagian besar dengan menyusun kembali menggunakan pernyataan if else. DangetValueOrDefault :: Maybe a -> (() -> a) -> a
memungkinkan Anda untuk menangani kasing kosong. Ini jauh lebih elegan daripada pencocokan polaMaybe a
secara eksplisit.Maybe
adalah cara berpikir masalah yang sangat fungsional - ada sesuatu, dan itu mungkin atau mungkin tidak memiliki nilai yang didefinisikan. Namun, dalam pengertian berorientasi objek, kami mengganti gagasan tentang suatu hal (tidak peduli apakah itu memiliki nilai atau tidak) dengan suatu objek. Jelas, suatu objek memiliki nilai. Jika tidak, kita mengatakan objeknyanull
, tetapi yang kita maksud sebenarnya adalah bahwa tidak ada objek sama sekali. Referensi yang kita miliki ke objek tidak menunjukkan apa-apa. MenerjemahkanMaybe
ke dalam konsep OO tidak melakukan apa-apa novel - pada kenyataannya, itu hanya membuat kode lebih berantakan. Anda masih harus memiliki referensi nol untuk nilaiMaybe<T>
. Anda masih harus melakukan pemeriksaan nol (pada kenyataannya, Anda harus melakukan lebih banyak pemeriksaan nol, mengacaukan kode Anda), bahkan jika sekarang disebut "mungkin cek". Tentu, Anda akan menulis kode yang lebih kuat seperti yang diklaim oleh penulis, tetapi saya berpendapat bahwa ini hanya masalahnya karena Anda telah membuat bahasanya jauh lebih abstrak dan tumpul, membutuhkan tingkat pekerjaan yang tidak perlu dalam kebanyakan kasus. Saya rela mengambil NullReferenceException sesekali daripada berurusan dengan kode spaghetti melakukan pemeriksaan Mungkin setiap kali saya mengakses variabel baru.sumber
Maybe<T>
harus mengizinkannull
sebagai nilai, karena nilainya mungkin tidak ada. Jika saya punyaMaybe<MyClass>
, dan tidak memiliki nilai, bidang nilai harus berisi referensi nol. Tidak ada hal lain untuk itu yang benar-benar aman.Maybe
bisa berupa kelas abstrak, dengan dua kelas yang mewarisi darinya:Some
(yang memang memiliki bidang untuk nilai) danNone
(yang tidak memiliki bidang itu). Dengan begitu, nilainya tidak pernahnull
.Konsep
null
dapat dengan mudah ditelusuri kembali ke C tetapi tidak di situlah masalahnya.Bahasa pilihan sehari-hari saya adalah C # dan saya akan tetap
null
dengan satu perbedaan. C # memiliki dua jenis tipe, nilai dan referensi . Nilai tidak pernah bisanull
, tetapi ada saatnya saya ingin bisa menyatakan bahwa tidak ada nilai yang baik-baik saja. Untuk melakukan ini C # menggunakanNullable
tipe sehinggaint
akan menjadi nilai danint?
nilai nullable. Ini adalah bagaimana saya pikir tipe referensi harus bekerja juga.Lihat juga: Referensi kosong mungkin bukan kesalahan :
sumber
Saya pikir ini karena pemrograman fungsional lebih memperhatikan tipe , terutama tipe yang menggabungkan tipe lain (tuple, fungsi sebagai tipe kelas satu, monad, dll.) Daripada pemrograman berorientasi objek (atau paling tidak pada awalnya memang demikian).
Versi modern dari bahasa pemrograman yang saya pikir Anda bicarakan (C ++, C #, Java) semuanya didasarkan pada bahasa yang tidak memiliki bentuk pemrograman generik (C, C # 1.0, Java 1). Tanpa itu, Anda masih dapat memanggang beberapa jenis antara objek nullable dan non-nullable ke dalam bahasa (seperti referensi C ++, yang tidak bisa
null
, tetapi juga terbatas), tetapi itu jauh kurang alami.sumber
Saya pikir alasan mendasar adalah bahwa relatif sedikit pemeriksaan nol diperlukan untuk membuat program "aman" terhadap korupsi data. Jika suatu program mencoba menggunakan konten elemen array atau lokasi penyimpanan lain yang seharusnya ditulis dengan referensi yang valid tetapi tidak, hasil kasus terbaik adalah pengecualian yang akan dibuang. Idealnya, pengecualian akan menunjukkan dengan tepat di mana masalah terjadi, tetapi yang penting adalah beberapa jenis pengecualian dilemparkan sebelum referensi nol disimpan di suatu tempat yang dapat menyebabkan korupsi data. Kecuali suatu metode menyimpan suatu objek tanpa mencoba menggunakannya terlebih dahulu, suatu upaya untuk menggunakan suatu objek akan - dengan sendirinya - merupakan semacam "pemeriksaan kosong".
Jika seseorang ingin memastikan bahwa referensi nol yang muncul di tempat yang seharusnya tidak menyebabkan pengecualian khusus selain
NullReferenceException
, sering kali perlu menyertakan cek nol di semua tempat. Di sisi lain, hanya memastikan bahwa beberapa pengecualian akan terjadi sebelum referensi nol dapat menyebabkan "kerusakan" di luar apa pun yang telah dilakukan akan sering memerlukan tes yang relatif sedikit - pengujian umumnya hanya diperlukan dalam kasus di mana suatu objek akan menyimpan referensi tanpa mencoba menggunakannya, dan baik referensi nol akan menimpa yang valid, atau akan menyebabkan kode lain salah menafsirkan aspek lain dari keadaan program. Situasi seperti itu ada, tetapi tidak terlalu umum; kebanyakan referensi nol yang tidak disengaja akan tertangkap dengan sangat cepatapakah orang memeriksa mereka atau tidak .sumber
"
Maybe
," seperti yang tertulis, adalah konstruk tingkat yang lebih tinggi daripada nol. Menggunakan lebih banyak kata untuk mendefinisikannya, Mungkin adalah, "pointer ke salah satu hal, atau pointer ke tidak ada, tetapi kompiler belum diberi informasi yang cukup untuk menentukan yang mana." Ini memaksa Anda untuk secara eksplisit memeriksa setiap nilai secara konstan, kecuali jika Anda membangun spesifikasi kompiler yang cukup pintar untuk mengikuti kode yang Anda tulis.Anda dapat membuat implementasi Maybe dengan bahasa yang memiliki null dengan mudah. C ++ memiliki satu dalam bentuk
boost::optional<T>
. Membuat yang setara dengan null dengan Maybe sangat sulit. Secara khusus, jika saya memilikiMaybe<Just<T>>
, saya tidak dapat menetapkannya ke nol (karena konsep seperti itu tidak ada), sedangkanT**
dalam bahasa dengan nol sangat mudah untuk menetapkan ke nol. Ini memaksa seseorang untuk menggunakanMaybe<Maybe<T>>
, yang benar-benar valid, tetapi akan memaksa Anda untuk melakukan lebih banyak pemeriksaan untuk menggunakan objek itu.Beberapa bahasa fungsional menggunakan Mungkin karena null memerlukan perilaku yang tidak terdefinisi atau penanganan perkecualian, yang keduanya tidak merupakan konsep yang mudah untuk dipetakan ke dalam sintaksis bahasa fungsional. Mungkin mengisi peran jauh lebih baik dalam situasi fungsional seperti itu, tetapi dalam bahasa prosedural, null adalah raja. Ini bukan masalah benar dan salah, hanya masalah apa yang membuatnya lebih mudah untuk memberitahu komputer untuk melakukan apa yang Anda inginkan.
sumber