Penjelasan terbaik untuk bahasa tanpa null

225

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)
Roman A. Taycher
sumber
11
Jika Anda menambahkan tag ke pertanyaan ini untuk pemrograman fungsional atau F # Anda pasti akan mendapatkan jawaban yang fantastis.
Stephen Swensen
Saya menambahkan tag pemrograman fungsional karena opsi-type memang berasal dari dunia ml. Saya lebih suka tidak menandainya F # (terlalu spesifik). BTW seseorang dengan kekuatan taksonomi perlu menambahkan tag tipe-tipe atau opsi-tipe.
Roman A. Taycher
4
ada sedikit kebutuhan untuk tag spesifik seperti itu, saya kira. Tag ini terutama untuk memungkinkan orang menemukan pertanyaan yang relevan (misalnya, "pertanyaan yang saya tahu banyak tentang, dan akan dapat menjawab", dan "pemrograman fungsional" sangat membantu di sana. Tetapi sesuatu seperti "null" atau " tipe-opsi "jauh lebih tidak berguna. Beberapa orang cenderung memantau tag" tipe-tipe "mencari pertanyaan yang dapat mereka jawab.;)
jalf
Jangan lupa bahwa salah satu alasan utama untuk null adalah bahwa komputer berevolusi sangat terkait dengan teori set. Null adalah salah satu set paling penting dalam semua teori himpunan. Tanpa itu seluruh algoritma akan rusak. Misalnya - melakukan semacam penggabungan. Ini melibatkan pemecahan daftar menjadi beberapa kali. Bagaimana jika daftar tersebut terdiri dari 7 item? Pertama Anda membaginya menjadi 4 dan 3. Kemudian 2, 2, 2, dan 1. Lalu 1, 1, 1, 1, 1, 1, 1, 1, dan .... null! Null memiliki tujuan, hanya satu yang tidak Anda lihat secara praktis. Itu ada lebih untuk ranah teoretis.
stevendesu
6
@steven_desu - Saya tidak setuju. Dalam bahasa 'nullable', Anda dapat memiliki referensi ke daftar kosong [], dan juga referensi daftar nol. Pertanyaan ini berkaitan dengan kebingungan di antara keduanya.
stusmith

Jawaban:

433

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

class Door
    private bool isShut
    private bool isLocked

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 mendefinisikan

type DoorState =
    | Open | ShutAndUnlocked | ShutAndLocked

maka saya bisa mendefinisikan

class Door
    private DoorState state

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 nulladalah bahwa setiap jenis referensi mendapatkan keadaan ekstra ini di ruangnya yang biasanya tidak diinginkan. Sebuah stringvariabel bisa menjadi salah urutan karakter, atau bisa juga tambahan ini gila nullnilai yang tidak memetakan ke domain masalah saya. Sebuah Triangleobjek memiliki tiga Points, yang sendiri memiliki Xdan Ynilai-nilai, tapi sayangnya Points atau Trianglesendiri mungkin menjadi ini nilai null gila yang berarti untuk domain grafik saya bekerja di. Dll

Ketika Anda bermaksud memodelkan nilai yang mungkin tidak ada, maka Anda harus memilihnya secara eksplisit. Jika cara saya berniat memodelkan orang adalah setiap orang Personmemiliki a FirstNamedan a LastName, tetapi hanya beberapa orang yang memiliki MiddleName, maka saya ingin mengatakan

class Person
    private string FirstName
    private Option<string> MiddleName
    private string LastName

di mana di stringsini diasumsikan sebagai tipe yang tidak dapat dibatalkan. Maka tidak ada invarian rumit untuk ditetapkan dan tidak ada yang tak terduga NullReferenceExceptionketika mencoba untuk menghitung panjang nama seseorang. Sistem tipe memastikan bahwa kode apa pun yang berhubungan dengan MiddleNameakun untuk kemungkinan itu None, sedangkan kode apa pun yang berurusan dengan FirstNamedapat dengan aman menganggap ada nilai di sana.

Jadi misalnya, menggunakan tipe di atas, kita bisa membuat fungsi konyol ini:

let TotalNumCharsInPersonsName(p:Person) =
    let middleLen = match p.MiddleName with
                    | None -> 0
                    | Some(s) -> s.Length
    p.FirstName.Length + middleLen + p.LastName.Length

tanpa khawatir. Sebaliknya, dalam bahasa dengan referensi nullable untuk tipe seperti string, lalu anggap

class Person
    private string FirstName
    private string MiddleName
    private string LastName

Anda akhirnya mengarang hal-hal seperti

let TotalNumCharsInPersonsName(p:Person) =
    p.FirstName.Length + p.MiddleName.Length + p.LastName.Length

yang meledak jika objek Orang yang masuk tidak memiliki invarian dari segala sesuatu yang bukan nol, atau

let TotalNumCharsInPersonsName(p:Person) =
    (if p.FirstName=null then 0 else p.FirstName.Length)
    + (if p.MiddleName=null then 0 else p.MiddleName.Length)
    + (if p.LastName=null then 0 else p.LastName.Length)

atau mungkin

let TotalNumCharsInPersonsName(p:Person) =
    p.FirstName.Length
    + (if p.MiddleName=null then 0 else p.MiddleName.Length)
    + p.LastName.Length

dengan asumsi bahwa pmemastikan 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 FirstNametidak bisa null, sebuah stringdapat 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 memiliki NonEmptyStringtipe). yang terakhir mungkin keliru (tipe "baik" sering "ditutup" pada serangkaian operasi umum, dan misalnya NonEmptyStringtidak 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.)

Brian
sumber
31
Re: names - Memang. Dan mungkin Anda benar-benar peduli untuk memodelkan pintu yang menggantung terbuka tetapi dengan kunci gerendel mencuat, mencegah pintu menutup. Ada banyak kompleksitas di dunia. Kuncinya adalah untuk tidak menambahkan lebih kompleksitas ketika melaksanakan pemetaan antara "negara dunia" dan "negara Program" dalam perangkat lunak Anda.
Brian
59
Apa, kamu tidak pernah mengunci pintu terbuka?
Yosua
58
Saya tidak mengerti mengapa orang-orang bekerja dengan semantik domain tertentu. Brian mewakili kekurangan dengan null secara ringkas dan sederhana, ya dia menyederhanakan domain masalah dalam contohnya dengan mengatakan setiap orang memiliki nama depan dan belakang. Pertanyaan itu dijawab untuk 'T', Brian - jika Anda pernah di boston, saya berhutang bir untuk semua posting yang Anda lakukan di sini!
akaphenom
67
@akaphenom: terima kasih, tetapi perhatikan bahwa tidak semua orang minum bir (saya bukan peminum). Tetapi saya menghargai bahwa Anda hanya menggunakan model dunia yang disederhanakan untuk menyampaikan rasa terima kasih, jadi saya tidak akan berdalih tentang asumsi cacat model dunia Anda. : P (Begitu banyak kerumitan di dunia nyata! :))
Brian
4
Anehnya, ada 3 pintu negara di dunia ini! Mereka digunakan di beberapa hotel sebagai pintu toilet. Tombol-tekan bertindak sebagai kunci dari dalam, yang mengunci pintu dari luar. Secara otomatis dibuka, segera setelah baut kait bergerak.
comonad
65

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:

  1. variabel tidak diinisialisasi. Ini seharusnya, idealnya, tidak pernah terjadi. Suatu variabel seharusnya tidak ada kecuali jika diinisialisasi.
  2. variabel berisi beberapa data "opsional": ia harus dapat mewakili kasus di mana tidak ada data . Ini kadang-kadang diperlukan. Mungkin Anda mencoba menemukan objek dalam daftar, dan Anda tidak tahu sebelumnya apakah ada di sana. Maka kita harus dapat menyatakan bahwa "tidak ada objek yang ditemukan".

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.

jalf
sumber
Dan dalam arti kedua, kita ingin kompiler memperingatkan (berhenti?) Kita jika kita mencoba mengakses variabel seperti itu tanpa terlebih dahulu memeriksa nullity. Berikut adalah artikel yang bagus tentang fitur C # null / non-null yang akan datang (akhirnya!) Blogs.msdn.microsoft.com/dotnet/2017/11/15/…
Ohad Schneider
44

Semua jawaban sejauh ini fokus pada mengapa nullitu 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 OptionatauMaybe 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 Optionsjuga.

Q. Jadi apa manfaatnya selain dapat menghapus nol dari suatu bahasa sepenuhnya?

SEBUAH. Komposisi

Jika Anda membuat terjemahan naif dari kode null-aware

def fullNameLength(p:Person) = {
  val middleLen =
    if (null == p.middleName)
      p.middleName.length
    else
      0
  p.firstName.length + middleLen + p.lastName.length
}

ke kode pilihan-sadar

def fullNameLength(p:Person) = {
  val middleLen = p.middleName match {
    case Some(x) => x.length
    case _ => 0
  }
  p.firstName.length + middleLen + p.lastName.length
}

tidak ada banyak perbedaan! Tetapi ini juga cara yang mengerikan untuk menggunakan Opsi ... Pendekatan ini jauh lebih bersih:

def fullNameLength(p:Person) = {
  val middleLen = p.middleName map {_.length} getOrElse 0
  p.firstName.length + middleLen + p.lastName.length
}

Atau bahkan:

def fullNameLength(p:Person) =       
  p.firstName.length +
  p.middleName.map{length}.getOrElse(0) +
  p.lastName.length

Ketika Anda mulai berurusan dengan Daftar Opsi, itu menjadi lebih baik. Bayangkan bahwa Daftar peopleitu sendiri opsional:

people flatMap(_ find (_.firstName == "joe")) map (fullNameLength)

Bagaimana cara kerjanya?

//convert an Option[List[Person]] to an Option[S]
//where the function f takes a List[Person] and returns an S
people map f

//find a person named "Joe" in a List[Person].
//returns Some[Person], or None if "Joe" isn't in the list
validPeopleList find (_.firstName == "joe")

//returns None if people is None
//Some(None) if people is valid but doesn't contain Joe
//Some[Some[Person]] if Joe is found
people map (_ find (_.firstName == "joe")) 

//flatten it to return None if people is None or Joe isn't found
//Some[Person] if Joe is found
people flatMap (_ find (_.firstName == "joe")) 

//return Some(length) if the list isn't None and Joe is found
//otherwise return None
people flatMap (_ find (_.firstName == "joe")) map (fullNameLength)

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.

Kevin Wright
sumber
8
+1, ini adalah poin yang baik untuk ditekankan. Satu tambahan: di Haskell-land, flatMapakan disebut (>>=), yaitu operator "bind" untuk monads. Itu benar, Haskellers sangat menyukai flatMapping hal-hal yang kita masukkan ke dalam logo bahasa kita.
CA McCann
1
+1 Semoga ungkapan 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)
Cukup mudah dilakukan: 'Daftar (nol) .headOption'. Perhatikan bahwa ini berarti hal yang sangat berbeda dari nilai pengembalian 'Tidak Ada'
Kevin Wright
4
Saya memberi Anda hadiah karena saya benar-benar menyukai apa yang Anda katakan tentang komposisi, yang tampaknya tidak disebutkan oleh orang lain.
Roman A. Taycher
Jawaban luar biasa dengan contoh-contoh hebat!
thSoft
38

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: nulladalah kasus tepi.

  • Benarkah null = null?
  • Benarkah nan = nan?
  • Benarkah inf = inf?
  • Benarkah +0 = -0?
  • Benarkah +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:

UNIQUE(a, b, id_a)
UNIQUE(a, b, id_b)

id_ahanya 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 seperti assert((bool)actionSheet + (bool)alertView == 1).

tc.
sumber
Matematikawan yang sebenarnya tidak menggunakan konsep "NaN", yakinlah.
Noldorin
@Noldorin: Ya, tetapi mereka menggunakan istilah "formulir tak tentu".
IJ Kennedy
@IJKennedy: Itu adalah perguruan tinggi yang berbeda, yang saya tahu dengan baik terima kasih. Beberapa NaN mungkin mewakili bentuk tak tentu, tetapi karena FPA tidak melakukan penalaran simbolis, menyamakannya dengan bentuk tak tentu cukup menyesatkan!
Noldorin
Ada apa dengan ini assert(actionSheet ^ alertView)? Atau bisakah bahasa Anda XOR bools?
kucing
16

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:

  1. Referensi / penunjuk tidak diinisialisasi: masalah di sini sama dengan mutabilitas secara umum. Untuk satu, itu membuatnya lebih sulit untuk menganalisis kode Anda.
  2. Variabel yang null sebenarnya berarti sesuatu: ini adalah kasus di mana tipe Opsi benar-benar diformalkan.

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 #:

//first we create the option list, and then filter out all None Option types and 
//map all Some Option types to their values.  See how type-inference shines.
let optionList = [Some(1); Some(2); None; Some(3); None]
optionList |> List.choose id //evaluates to [1;2;3]

//here is a simple pattern-matching example
//which prints "1;2;None;3;None;".
//notice how value is extracted from op during the match
optionList 
|> List.iter (function Some(value) -> printf "%i;" value | None -> printf "None;")

Namun, dalam bahasa seperti Java tanpa dukungan langsung untuk jenis Opsi, kami akan memiliki sesuatu seperti:

//here we perform the same filter/map operation as in the F# example.
List<Option<Integer>> optionList = Arrays.asList(new Some<Integer>(1),new Some<Integer>(2),new None<Integer>(),new Some<Integer>(3),new None<Integer>());
List<Integer> filteredList = new ArrayList<Integer>();
for(Option<Integer> op : list)
    if(op instanceof Some)
        filteredList.add(((Some<Integer>)op).getValue());

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.

Stephen Swensen
sumber
Ini adalah hewan peliharaan kesal tapi c # hampir tidak seperti bahasa c.
Roman A. Taycher
4
Saya pergi ke Jawa di sini, karena C # mungkin akan memiliki solusi yang lebih baik ... tapi saya menghargai kencing Anda, apa yang orang-orang maksudkan sebenarnya adalah "bahasa dengan sintaks yang diilhami c". Saya melanjutkan dan mengganti pernyataan "c-like".
Stephen Swensen
Dengan LINQ, benar. Saya sedang memikirkan c # dan tidak menyadarinya.
Roman A. Taycher
1
Ya dengan sebagian besar sintaks yang diilhami c, tapi saya pikir saya juga pernah mendengar tentang bahasa pemrograman imperatif seperti python / ruby ​​dengan sedikit cara c seperti sintaksis yang disebut sebagai c-like oleh programmer fungsional.
Roman A. Taycher
11

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:

  • Memberitahu bahwa ada sesuatu yang tidak jelas .
  • Mengatakan bahwa sesuatu itu opsional .

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:

struct PhoneNumber { ... };
struct MotorbikeLicence { ... };
struct CarLicence { ... };
struct TruckLicence { ... };

struct Driver {
  char name[32]; /* Null terminated */
  struct PhoneNumber * emergency_phone_number;
  struct MotorbikeLicence * motorbike_licence;
  struct CarLicence * car_licence;
  struct TruckLicence * truck_licence;
};

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:

type phone_number = { ... }
type motorbike_licence = { ... }
type car_licence = { ... }
type truck_licence = { ... }

type driver = {
  name: string;
  emergency_phone_number: phone_number option;
  motorbike_licence: motorbike_licence option;
  car_licence: car_licence option;
  truck_licence: truck_licence option;
}

Sekarang mari kita katakan bahwa kita ingin mencetak nama semua pengemudi bersama dengan nomor lisensi truk mereka.

Dalam C:

#include <stdio.h>

void print_driver_with_truck_licence_number(struct Driver * driver) {
  /* Check may be redundant but better be safe than sorry */
  if (driver != NULL) {
    printf("driver %s has ", driver->name);
    if (driver->truck_licence != NULL) {
      printf("truck licence %04d-%04d-%08d\n",
        driver->truck_licence->area_code
        driver->truck_licence->year
        driver->truck_licence->num_in_year);
    } else {
      printf("no truck licence\n");
    }
  }
}

void print_drivers_with_truck_licence_numbers(struct Driver ** drivers, int nb) {
  if (drivers != NULL && nb >= 0) {
    int i;
    for (i = 0; i < nb; ++i) {
      struct Driver * driver = drivers[i];
      if (driver) {
        print_driver_with_truck_licence_number(driver);
      } else {
        /* Huh ? We got a null inside the array, meaning it probably got
           corrupt somehow, what do we do ? Ignore ? Assert ? */
      }
    }
  } else {
    /* Caller provided us with erroneous input, what do we do ?
       Ignore ? Assert ? */
  }
}

Dalam OCaml itu adalah:

open Printf

(* Here we are guaranteed to have a driver instance *)
let print_driver_with_truck_licence_number driver =
  printf "driver %s has " driver.name;
  match driver.truck_licence with
    | None ->
        printf "no truck licence\n"
    | Some licence ->
        (* Here we are guaranteed to have a licence *)
        printf "truck licence %04d-%04d-%08d\n"
          licence.area_code
          licence.year
          licence.num_in_year

(* Here we are guaranteed to have a valid list of drivers *)
let print_drivers_with_truck_licence_numbers drivers =
  List.iter print_driver_with_truck_licence_number drivers

Seperti yang Anda lihat dalam contoh sepele ini, tidak ada yang rumit di versi aman:

  • Ini terser.
  • Anda mendapatkan jaminan yang jauh lebih baik dan tidak ada pemeriksaan nol yang diperlukan sama sekali.
  • Kompiler memastikan bahwa Anda menangani opsi dengan benar

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.

bltxd
sumber
Saya belum pernah mencobanya tetapi en.wikipedia.org/wiki/Cyclone_%28programming_language%29 mengklaim untuk mengizinkan pointer non-null untuk c.
Roman A. Taycher
1
Saya tidak setuju dengan pernyataan Anda bahwa tidak ada yang tertarik pada kasus pertama. Banyak orang, terutama mereka yang berada dalam komunitas bahasa fungsional, sangat tertarik dengan hal ini dan tidak mendukung atau sepenuhnya melarang penggunaan variabel yang tidak diinisialisasi.
Stephen Swensen
Saya percaya NULLdalam "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).
1
@Stephen: Kami mungkin bermaksud hal yang sama. Bagi saya mereka mencegah atau melarang penggunaan hal-hal yang tidak diinisialisasi justru karena tidak ada gunanya membahas hal-hal yang tidak terdefinisi karena kita tidak dapat melakukan sesuatu yang waras atau berguna dengannya. Sama sekali tidak tertarik.
bltxd
2
sebagai @tc. mengatakan, null tidak ada hubungannya dengan perakitan. Dalam perakitan, jenis umumnya tidak dapat dibatalkan. Nilai yang dimuat ke register tujuan umum mungkin nol atau mungkin bilangan bulat bukan nol. Tapi itu tidak pernah bisa menjadi nol. Bahkan jika Anda memuat alamat memori ke dalam register, pada kebanyakan arsitektur umum, tidak ada representasi terpisah dari "null pointer". Itu konsep yang diperkenalkan dalam bahasa tingkat tinggi, seperti C.
jalf
5

Microsoft Research memiliki proyek intersting yang disebut

Spesifikasi #

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.

Jahan
sumber
4

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:

Saya menyebutnya kesalahan miliaran dolar saya. Itu adalah penemuan referensi nol pada tahun 1965. Pada waktu itu, saya sedang merancang sistem tipe komprehensif pertama untuk referensi dalam bahasa berorientasi objek (ALGOL W). Tujuan saya adalah untuk memastikan bahwa semua penggunaan referensi harus benar-benar aman, dengan pengecekan dilakukan secara otomatis oleh kompiler. Tapi saya tidak bisa menahan godaan untuk memasukkan referensi nol, hanya karena itu sangat mudah diimplementasikan. Ini telah menyebabkan kesalahan yang tak terhitung banyaknya, kerentanan, dan crash sistem, yang mungkin telah menyebabkan satu miliar dolar rasa sakit dan kerusakan dalam empat puluh tahun terakhir. Dalam beberapa tahun terakhir, sejumlah penganalisa program seperti PREfix dan PREfast di Microsoft telah digunakan untuk memeriksa referensi, dan memberikan peringatan jika ada risiko mereka mungkin tidak nol. Bahasa pemrograman yang lebih baru seperti Spec # telah memperkenalkan deklarasi untuk referensi bukan nol. Ini solusinya, yang saya tolak pada tahun 1965.

Lihat pertanyaan ini juga di programmer

nawfal
sumber
1

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.

Jon
sumber
Hai @ Jon, agak sulit mengikuti Anda di sini. Saya akhirnya menyadari bahwa dengan nilai "spesial / aneh" Anda mungkin berarti sesuatu seperti Javascript 'tidak terdefinisi' atau IEEE 'NaN'. Tapi selain itu, Anda tidak benar-benar menjawab pertanyaan yang diajukan OP. Dan pernyataan bahwa "Null mungkin adalah gagasan yang paling berguna untuk memeriksa apakah ada sesuatu yang hilang" hampir pasti salah. Jenis opsi adalah alternatif yang dianggap baik dan aman bagi null.
Stephen Swensen
@Stephen - Sebenarnya melihat kembali ke pesan saya, saya pikir seluruh babak ke-2 harus dipindahkan ke pertanyaan yang belum diajukan. Tapi saya masih mengatakan null sangat berguna untuk memeriksa untuk melihat apakah ada yang tidak ada.
Jon
0

Bahasa vektor terkadang bisa lolos dengan tidak memiliki null.

Vektor kosong berfungsi sebagai null yang diketik dalam kasus ini.

Joshua
sumber
Saya pikir saya mengerti apa yang Anda bicarakan tetapi bisakah Anda menuliskan beberapa contoh? Terutama menerapkan beberapa fungsi ke nilai yang mungkin nol?
Roman A. Taycher
Menerapkan transformasi vektor ke vektor kosong akan menghasilkan vektor kosong lainnya. FYI, SQL sebagian besar merupakan bahasa vektor.
Yosua
1
OK saya lebih baik mengklarifikasi itu. SQL adalah bahasa vektor untuk baris dan bahasa nilai untuk kolom.
Yosua