Sering kali ketika programmer mengeluh tentang kesalahan / pengecualian nol seseorang bertanya apa yang kita lakukan tanpa nol.
Saya punya beberapa ide dasar tentang kesejukan jenis-jenis opsi, tetapi saya tidak memiliki pengetahuan atau keterampilan bahasa untuk mengekspresikannya. Apa penjelasan hebat dari yang berikut yang ditulis dengan cara yang dapat didekati oleh programmer rata-rata yang bisa kita arahkan ke orang itu?
- Ketidaksukaan memiliki referensi / pointer menjadi nullable secara default
- Cara kerja jenis opsi termasuk strategi untuk memudahkan memeriksa kasus nol seperti
- pencocokan pola dan
- pemahaman monadik
- Solusi alternatif seperti pesan makan nihil
- (aspek lain yang saya lewatkan)
programming-languages
functional-programming
null
nullpointerexception
non-nullable
Roman A. Taycher
sumber
sumber
Jawaban:
Saya pikir ringkasan singkat mengapa nol tidak diinginkan adalah bahwa keadaan yang tidak berarti tidak dapat diwakili .
Misalkan saya sedang membuat model sebuah pintu. Itu bisa di salah satu dari tiga negara: buka, tutup tetapi tidak terkunci, dan tutup dan dikunci. Sekarang saya bisa memodelkannya di sepanjang garis
dan jelas bagaimana memetakan ketiga status saya ke dalam dua variabel boolean ini. Tapi daun ini keempat, negara yang tidak diinginkan tersedia:
isShut==false && isLocked==true
. Karena tipe yang saya pilih sebagai representasi saya mengakui keadaan ini, saya harus mengeluarkan upaya mental untuk memastikan bahwa kelas tidak pernah masuk ke keadaan ini (mungkin dengan secara eksplisit mengkode invarian). Sebaliknya, jika saya menggunakan bahasa dengan tipe data aljabar atau memeriksa enumerasi yang memungkinkan saya mendefinisikanmaka saya bisa mendefinisikan
dan tidak ada lagi kekhawatiran. Sistem tipe akan memastikan bahwa hanya ada tiga kemungkinan status untuk instance
class Door
. Inilah tipe sistem yang baik - secara eksplisit mengesampingkan seluruh kelas kesalahan pada waktu kompilasi.Masalahnya
null
adalah bahwa setiap jenis referensi mendapatkan keadaan ekstra ini di ruangnya yang biasanya tidak diinginkan. Sebuahstring
variabel bisa menjadi salah urutan karakter, atau bisa juga tambahan ini gilanull
nilai yang tidak memetakan ke domain masalah saya. SebuahTriangle
objek memiliki tigaPoint
s, yang sendiri memilikiX
danY
nilai-nilai, tapi sayangnyaPoint
s atauTriangle
sendiri mungkin menjadi ini nilai null gila yang berarti untuk domain grafik saya bekerja di. DllKetika Anda bermaksud memodelkan nilai yang mungkin tidak ada, maka Anda harus memilihnya secara eksplisit. Jika cara saya berniat memodelkan orang adalah setiap orang
Person
memiliki aFirstName
dan aLastName
, tetapi hanya beberapa orang yang memilikiMiddleName
, maka saya ingin mengatakandi mana di
string
sini diasumsikan sebagai tipe yang tidak dapat dibatalkan. Maka tidak ada invarian rumit untuk ditetapkan dan tidak ada yang tak terdugaNullReferenceException
ketika mencoba untuk menghitung panjang nama seseorang. Sistem tipe memastikan bahwa kode apa pun yang berhubungan denganMiddleName
akun untuk kemungkinan ituNone
, sedangkan kode apa pun yang berurusan denganFirstName
dapat dengan aman menganggap ada nilai di sana.Jadi misalnya, menggunakan tipe di atas, kita bisa membuat fungsi konyol ini:
tanpa khawatir. Sebaliknya, dalam bahasa dengan referensi nullable untuk tipe seperti string, lalu anggap
Anda akhirnya mengarang hal-hal seperti
yang meledak jika objek Orang yang masuk tidak memiliki invarian dari segala sesuatu yang bukan nol, atau
atau mungkin
dengan asumsi bahwa
p
memastikan pertama / terakhir ada tetapi tengah bisa nol, atau mungkin Anda melakukan pengecekan yang membuang berbagai jenis pengecualian, atau siapa yang tahu apa. Semua pilihan implementasi gila dan hal-hal untuk dipikirkan ini muncul karena ada nilai representatif bodoh yang tidak Anda inginkan atau butuhkan.Null biasanya menambah kompleksitas yang tidak perlu. Kompleksitas adalah musuh dari semua perangkat lunak, dan Anda harus berusaha mengurangi kompleksitas kapan pun wajar.
(Catat juga bahwa ada lebih banyak kompleksitas pada contoh-contoh sederhana ini. Bahkan jika
FirstName
tidak bisanull
, sebuahstring
dapat mewakili""
(string kosong), yang mungkin juga bukan nama orang yang ingin kita modelkan. Dengan demikian, bahkan dengan non- string nullable, mungkin masih menjadi kasus bahwa kami "mewakili nilai yang tidak berarti". Sekali lagi, Anda dapat memilih untuk melawan ini baik melalui invarian dan kode kondisional pada saat runtime, atau dengan menggunakan sistem tipe (misalnya untuk memilikiNonEmptyString
tipe). yang terakhir mungkin keliru (tipe "baik" sering "ditutup" pada serangkaian operasi umum, dan misalnyaNonEmptyString
tidak ditutup.SubString(0,0)
), tetapi menunjukkan lebih banyak poin di ruang desain. Pada akhirnya, dalam sistem tipe apa pun, ada beberapa kompleksitas yang akan sangat baik untuk dihilangkan, dan kompleksitas lain yang secara intrinsik lebih sulit untuk dihilangkan. Kunci untuk topik ini adalah bahwa di hampir setiap sistem tipe, perubahan dari "referensi nullable secara default" ke "referensi non-nullable secara default" hampir selalu merupakan perubahan sederhana yang membuat sistem tipe jauh lebih baik dalam mengatasi kompleksitas dan mengesampingkan jenis kesalahan tertentu dan keadaan tidak berarti. Jadi sangat gila bahwa begitu banyak bahasa terus mengulangi kesalahan ini berulang kali.)sumber
Hal yang menyenangkan tentang tipe opsi bukanlah bahwa mereka opsional. Itu adalah bahwa semua jenis lain tidak .
Terkadang , kita harus bisa mewakili semacam "null" state. Terkadang kita harus mewakili opsi "tidak ada nilai" dan juga nilai lain yang mungkin diambil oleh variabel. Jadi bahasa yang tidak mengizinkan ini akan menjadi sedikit cacat.
Tetapi seringkali , kita tidak membutuhkannya, dan membiarkan keadaan "null" seperti itu hanya mengarah pada ambiguitas dan kebingungan: setiap kali saya mengakses variabel tipe referensi di .NET, saya harus mempertimbangkan bahwa itu mungkin nol .
Seringkali, itu tidak akan pernah terjadi benar - benar nol, karena programmer menyusun kode sehingga tidak pernah bisa terjadi. Tetapi kompiler tidak dapat memverifikasi itu, dan setiap kali Anda melihatnya, Anda harus bertanya pada diri sendiri "bisakah ini nol? Apakah saya perlu memeriksa nol di sini?"
Idealnya, dalam banyak kasus di mana null tidak masuk akal, seharusnya tidak diizinkan .
Itu sulit dicapai di .NET, di mana hampir semuanya bisa menjadi nol. Anda harus mengandalkan pembuat kode yang Anda panggil untuk menjadi 100% disiplin dan konsisten dan telah dengan jelas mendokumentasikan apa yang bisa dan tidak bisa menjadi nol, atau Anda harus paranoid dan memeriksa semuanya .
Namun, jika jenisnya tidak dapat dibatalkan secara default , maka Anda tidak perlu memeriksa apakah itu null atau tidak. Anda tahu mereka tidak akan pernah menjadi nol, karena pemeriksa / pemeriksa tipe memberlakukannya untuk Anda.
Dan kemudian kita hanya perlu pintu belakang untuk kasus-kasus langka di mana kita melakukannya kebutuhan untuk menangani keadaan nol. Kemudian tipe "opsi" dapat digunakan. Lalu kami mengizinkan nol dalam kasus di mana kami telah membuat keputusan sadar bahwa kami harus dapat mewakili kasus "tidak ada nilai", dan dalam setiap kasus lainnya, kami tahu bahwa nilainya tidak akan pernah menjadi nol.
Seperti yang disebutkan orang lain, dalam C # atau Java misalnya, null dapat berarti satu dari dua hal:
Makna kedua harus dipertahankan, tetapi yang pertama harus dihilangkan seluruhnya. Dan bahkan arti kedua seharusnya tidak menjadi standar. Ini adalah sesuatu yang dapat kita pilih jika dan ketika kita membutuhkannya . Tetapi ketika kita tidak membutuhkan sesuatu untuk menjadi opsional, kami ingin pemeriksa tipe menjamin bahwa itu tidak akan pernah menjadi nol.
sumber
Semua jawaban sejauh ini fokus pada mengapa
null
itu hal yang buruk, dan bagaimana itu agak berguna jika suatu bahasa dapat menjamin bahwa nilai-nilai tertentu tidak akan pernah menjadi nol.Mereka kemudian melanjutkan untuk menyarankan bahwa itu akan menjadi ide yang cukup rapi jika Anda menegakkan non-nullability untuk semua nilai, yang dapat dilakukan jika Anda menambahkan konsep suka
Option
atauMaybe
untuk mewakili tipe yang mungkin tidak selalu memiliki nilai yang ditentukan. Ini adalah pendekatan yang diambil oleh Haskell.Semuanya bagus! Tapi itu tidak menghalangi penggunaan tipe nullable / non-null secara eksplisit untuk mencapai efek yang sama. Lalu, mengapa Option masih merupakan hal yang baik? Setelah semua, Scala mendukung nilai-nilai nullable (adalah memiliki untuk, sehingga dapat bekerja dengan perpustakaan Java) tapi dukungan
Options
juga.Q. Jadi apa manfaatnya selain dapat menghapus nol dari suatu bahasa sepenuhnya?
SEBUAH. Komposisi
Jika Anda membuat terjemahan naif dari kode null-aware
ke kode pilihan-sadar
tidak ada banyak perbedaan! Tetapi ini juga cara yang mengerikan untuk menggunakan Opsi ... Pendekatan ini jauh lebih bersih:
Atau bahkan:
Ketika Anda mulai berurusan dengan Daftar Opsi, itu menjadi lebih baik. Bayangkan bahwa Daftar
people
itu sendiri opsional:Bagaimana cara kerjanya?
Kode yang sesuai dengan cek nol (atau bahkan elvis?: Operator) akan sangat panjang. Trik sebenarnya di sini adalah operasi flatMap, yang memungkinkan untuk pemahaman bersarang dari Opsi dan koleksi dengan cara yang nilai-nilai nullable tidak pernah dapat dicapai.
sumber
flatMap
akan disebut(>>=)
, yaitu operator "bind" untuk monads. Itu benar, Haskellers sangat menyukaiflatMap
ping hal-hal yang kita masukkan ke dalam logo bahasa kita.Option<T>
tidak akan pernah menjadi nol. Sayangnya, Scala adalah uhh, masih terhubung ke Jawa :-) (Di sisi lain, jika Scala tidak bermain bagus dengan Java, siapa yang akan menggunakannya? Oo)Karena orang sepertinya melewatkannya:
null
ambigu.Tanggal kelahiran Alice adalah
null
. Apa artinya?Tanggal kematian Bob adalah
null
. Apa artinya?Penafsiran yang "masuk akal" mungkin bahwa tanggal kelahiran Alice ada tetapi tidak diketahui, sedangkan tanggal kematian Bob tidak ada (Bob masih hidup). Tetapi mengapa kita mendapatkan jawaban yang berbeda?
Masalah lain:
null
adalah kasus tepi.null = null
?nan = nan
?inf = inf
?+0 = -0
?+0/0 = -0/0
?Jawabannya biasanya masing-masing "ya", "tidak", "ya", "ya", "tidak", "ya". "Matematikawan" yang gila menyebut NaN "nullity" dan mengatakan itu sebanding dengan dirinya sendiri. SQL memperlakukan nulls sebagai tidak sama dengan apa pun (sehingga mereka berperilaku seperti NaNs). Orang bertanya-tanya apa yang terjadi ketika Anda mencoba menyimpan ± ∞, ± 0, dan NaNs ke dalam kolom database yang sama (ada 2 53 NaNs, setengahnya adalah "negatif").
Untuk membuat keadaan menjadi lebih buruk, database berbeda dalam cara mereka memperlakukan NULL, dan kebanyakan dari mereka tidak konsisten (lihat Penanganan NULL di SQLite untuk ikhtisar). Cukup mengerikan.
Dan sekarang untuk kisah wajib:
Saya baru-baru ini merancang tabel database (sqlite3) dengan lima kolom
a NOT NULL, b, id_a, id_b NOT NULL, timestamp
. Karena ini adalah skema umum yang dirancang untuk menyelesaikan masalah umum untuk aplikasi yang sewenang-wenang, ada dua kendala keunikan:id_a
hanya ada untuk kompatibilitas dengan desain aplikasi yang ada (sebagian karena saya belum menemukan solusi yang lebih baik), dan tidak digunakan dalam aplikasi baru. Karena cara NULL bekerja di SQL, saya bisa menyisipkan(1, 2, NULL, 3, t)
dan(1, 2, NULL, 4, t)
dan tidak melanggar kendala keunikan pertama (karena(1, 2, NULL) != (1, 2, NULL)
).Ini bekerja secara khusus karena cara kerja NULL dalam batasan keunikan pada sebagian besar basis data (mungkin sehingga lebih mudah untuk memodelkan situasi "dunia nyata", mis. Tidak ada dua orang yang dapat memiliki Nomor Jaminan Sosial yang sama, tetapi tidak semua orang memilikinya).
FWIW, tanpa terlebih dahulu menerapkan perilaku tidak terdefinisi, referensi C ++ tidak dapat "menunjuk ke" nol, dan tidak mungkin untuk membangun kelas dengan variabel anggota referensi yang tidak diinisialisasi (jika pengecualian dilemparkan, konstruksi gagal).
Sidenote: Kadang-kadang Anda mungkin ingin pointer yang saling eksklusif (yaitu hanya satu dari mereka yang bisa non-NULL), misalnya dalam iOS hipotetis
type DialogState = NotShown | ShowingActionSheet UIActionSheet | ShowingAlertView UIAlertView | Dismissed
. Sebaliknya, saya terpaksa melakukan hal-hal sepertiassert((bool)actionSheet + (bool)alertView == 1)
.sumber
assert(actionSheet ^ alertView)
? Atau bisakah bahasa Anda XOR bools?Ketidaksukaan memiliki referensi / pointer menjadi nullable secara default.
Saya tidak berpikir ini adalah masalah utama dengan nol, masalah utama dengan nol adalah bahwa mereka dapat berarti dua hal:
Bahasa yang mendukung tipe Opsi biasanya juga melarang atau menghambat penggunaan variabel yang tidak diinisialisasi juga.
Cara kerja jenis opsi termasuk strategi untuk memudahkan memeriksa kasus nol seperti pencocokan pola.
Agar efektif, jenis Opsi perlu didukung langsung dalam bahasa. Kalau tidak, dibutuhkan banyak kode boiler-plate untuk mensimulasikan mereka. Pencocokan pola dan inferensi jenis adalah dua fitur bahasa utama yang membuat jenis Opsi mudah digunakan. Sebagai contoh:
Dalam F #:
Namun, dalam bahasa seperti Java tanpa dukungan langsung untuk jenis Opsi, kami akan memiliki sesuatu seperti:
Solusi alternatif seperti pesan makan nihil
"Pesan-makan nihil" yang obyektif-C bukan merupakan solusi sebagai upaya meringankan sakit kepala dari pemeriksaan nol. Pada dasarnya, alih-alih melempar pengecualian runtime ketika mencoba memanggil metode pada objek nol, ekspresi sebaliknya dievaluasi menjadi nol sendiri. Menangguhkan ketidakpercayaan, seolah-olah setiap metode contoh dimulai dengan
if (this == null) return null;
. Tetapi kemudian ada kehilangan informasi: Anda tidak tahu apakah metode mengembalikan nol karena itu adalah nilai pengembalian yang valid, atau karena objek tersebut sebenarnya nol. Ini seperti pengecualian menelan, dan tidak membuat kemajuan dalam mengatasi masalah dengan nol yang diuraikan sebelumnya.sumber
Majelis memberi kami alamat yang juga dikenal sebagai pointer yang tidak diketik. C memetakannya secara langsung sebagai pointer yang diketikkan tetapi memperkenalkan null Algol sebagai nilai pointer unik, kompatibel dengan semua pointer yang diketik. Masalah besar dengan null di C adalah karena setiap pointer bisa menjadi null, kita tidak pernah bisa menggunakan pointer dengan aman tanpa pemeriksaan manual.
Dalam bahasa tingkat yang lebih tinggi, memiliki null adalah canggung karena benar-benar menyampaikan dua gagasan berbeda:
Memiliki variabel yang tidak terdefinisi sangat tidak berguna, dan menghasilkan perilaku yang tidak terdefinisi kapan pun mereka terjadi. Saya kira semua orang akan setuju bahwa memiliki hal-hal yang tidak terdefinisi harus dihindari dengan cara apa pun.
Kasus kedua adalah opsional dan paling baik diberikan secara eksplisit, misalnya dengan jenis opsi .
Katakanlah kita berada di perusahaan transportasi dan kita perlu membuat aplikasi untuk membantu membuat jadwal untuk pengemudi kami. Untuk setiap pengemudi, kami menyimpan beberapa informasi seperti: SIM yang mereka miliki dan nomor telepon untuk dihubungi jika terjadi keadaan darurat.
Dalam C kita bisa memiliki:
Seperti yang Anda amati, dalam pemrosesan apa pun atas daftar driver kami, kami harus memeriksa pointer nol. Kompiler tidak akan membantu Anda, keamanan program bergantung pada pundak Anda.
Di OCaml, kode yang sama akan terlihat seperti ini:
Sekarang mari kita katakan bahwa kita ingin mencetak nama semua pengemudi bersama dengan nomor lisensi truk mereka.
Dalam C:
Dalam OCaml itu adalah:
Seperti yang Anda lihat dalam contoh sepele ini, tidak ada yang rumit di versi aman:
Sedangkan di C, Anda bisa saja lupa cek nol dan booming ...
Catatan: contoh kode ini di mana tidak dikompilasi, tapi saya harap Anda punya ide.
sumber
NULL
dalam "referensi yang mungkin tidak mengarah ke apa pun" diciptakan untuk beberapa bahasa Algol (Wikipedia setuju, lihat en.wikipedia.org/wiki/Null_pointer#Null_pointer ). Tapi tentu saja itu kemungkinan bahwa programer perakitan menginisialisasi pointer mereka ke alamat yang tidak valid (baca: Null = 0).Microsoft Research memiliki proyek intersting yang disebut
Ini adalah ekstensi C # dengan tipe bukan-nol dan beberapa mekanisme untuk memeriksa objek Anda agar tidak menjadi nol , meskipun, IMHO, menerapkan desain dengan prinsip kontrak mungkin lebih tepat dan lebih bermanfaat untuk banyak situasi sulit yang disebabkan oleh referensi nol.
sumber
Berasal dari latar belakang .NET, saya selalu berpikir nol ada benarnya, berguna. Sampai saya mengetahui tentang struct dan betapa mudahnya bekerja dengan mereka menghindari banyak kode boilerplate. Tony Hoare berbicara di QCon London pada 2009, meminta maaf karena menemukan referensi nol . Mengutipnya:
Lihat pertanyaan ini juga di programmer
sumber
Robert Nystrom menawarkan artikel yang bagus di sini:
http://journal.stuffwithstuff.com/2010/08/23/void-null-maybe-and-nothing/
menggambarkan proses pemikirannya ketika menambahkan dukungan untuk ketidakhadiran dan kegagalan bahasa pemrograman Magpie- nya .
sumber
Saya selalu memandang Null (atau nihil) sebagai tidak adanya nilai .
Terkadang Anda menginginkan ini, kadang tidak. Itu tergantung pada domain Anda bekerja dengan. Jika absennya bermakna: tidak ada nama tengah, maka aplikasi Anda dapat bertindak sesuai. Di sisi lain jika nilai nol tidak boleh ada di sana: Nama depan adalah nol, maka pengembang mendapatkan panggilan telepon pepatah 02:00.
Saya juga melihat kode kelebihan beban dan terlalu rumit dengan cek nol. Bagi saya ini berarti salah satu dari dua hal:
a) bug yang lebih tinggi di pohon aplikasi
b) desain buruk / tidak lengkap
Di sisi positif - Null mungkin salah satu gagasan yang lebih berguna untuk memeriksa apakah ada sesuatu yang tidak ada, dan bahasa tanpa konsep nol akan menambah hal-hal yang terlalu rumit ketika saatnya untuk melakukan validasi data. Dalam hal ini, jika variabel baru tidak diinisialisasi, kata bahasanya biasanya akan mengatur variabel ke string kosong, 0, atau koleksi kosong. Namun, jika string kosong atau 0 atau koleksi kosong adalah nilai yang valid untuk aplikasi Anda - maka Anda memiliki masalah.
Kadang-kadang ini dielakkan dengan menciptakan nilai khusus / aneh untuk bidang untuk mewakili keadaan tidak diinisialisasi. Tetapi kemudian apa yang terjadi ketika nilai khusus dimasukkan oleh pengguna yang bermaksud baik? Dan jangan sampai berantakan ini akan membuat rutinitas validasi data. Jika bahasa mendukung konsep nol, semua masalah akan hilang.
sumber
Bahasa vektor terkadang bisa lolos dengan tidak memiliki null.
Vektor kosong berfungsi sebagai null yang diketik dalam kasus ini.
sumber