Чому більшість "відомих" імперативних мов / OO дозволяють неперевірений доступ до типів, які можуть представляти значення "нічого"?


29

Я читав про (не) зручність мати nullзамість (наприклад) Maybe. Прочитавши цю статтю , я переконаний, що було б набагато краще використовуватиMaybe (або щось подібне). Однак я здивований, побачивши, що всі "відомі" імперативні або об'єктно-орієнтовані мови програмування все ще використовують null(що дозволяє неперевірений доступ до типів, які можуть представляти значення "нічого"), і Maybeце здебільшого використовується у функціональних мовах програмування.

Як приклад, подивіться наступний код C #:

void doSomething(string username)
{
    // Check that username is not null
    // Do something
}

Тут щось погано пахне ... Навіщо нам перевіряти, чи аргумент недійсний? Чи не слід вважати, що кожна змінна містить посилання на об'єкт? Як бачите, проблема полягає в тому, що за визначенням майже всі змінні можуть містити нульову посилання. Що робити, якщо ми могли вирішити, які змінні є "нульовими", а які ні? Це дозволить заощадити багато зусиль під час налагодження та пошуку "NullReferenceException". Уявіть, що за замовчуванням жоден тип не може містити нульової посилання . Замість цього ви б чітко заявили, що змінна може містити нульову посилання , лише якщо вона вам справді потрібна. Це ідея, можливо, Якщо у вас є функція, яка в деяких випадках не спрацьовує (наприклад, ділення на нуль), ви можете повернути aMaybe<int>, прямо заявляючи, що результат може бути цілим, але також нічого! Це одна причина віддати перевагу Можливо, а не нульову. Якщо вас цікавить більше прикладів, то пропоную прочитати цю статтю .

Факти полягають у тому, що, незважаючи на недоліки змушення більшості типів за замовчуванням, більшість мов програмування OO насправді це роблять. Ось чому мені цікаво:

  • Які аргументи ви б мали застосувати nullзамість своєї мови програмування Maybe? Чи є взагалі причини чи це просто "історичний багаж"?

Будь ласка, переконайтеся, що ви розумієте різницю між null та Maybe, перш ніж відповісти на це запитання.


3
Я пропоную прочитати про Тоні Хоара , зокрема про його помилку в мільярд доларів.
Oded

2
Ну, це була його причина. І невдалий результат полягає в тому, що це було помилкою, скопійованою на більшості мов, які випливали, до сьогодні. Там є мови , де nullі його концепція не існує (IIRC Haskell є одним з таких прикладів).
Oded

9
Історичний багаж - це не те, що варто недооцінювати. І пам’ятайте, що ОС, на основі яких були побудовані, були написані мовами, які були nullв них давно. Це просто не можна просто скинути.
Oded

3
Я думаю, вам слід пролити трохи світла на концепцію, можливо, замість публікації посилання.
JeffO

1
@ GlenH7 Рядки в C # є еталонними значеннями (вони можуть бути нульовими). Я знаю, що int є примітивною цінністю, але було корисно показати використання, можливо.
aochagavia

Відповіді:


15

Я вважаю, що це насамперед історичний багаж.

Найвідоміша і найстаріша мова з null- це C і C ++. Але тут, nullмає сенс. Покажчики - це все ще досить чисельне та низькорівневе поняття. І як хтось інший сказав, що в думці програмістів на C і C ++ необхідність чітко сказати, що покажчик може бути nullне має сенсу.

Другим на черзі виходить Java. Зважаючи на те, що розробники Java намагалися наблизитись до C ++, тому вони могли зробити перехід від C ++ до Java більш простим, вони, ймовірно, не хотіли возитися з такою основною концепцією мови. Крім того, реалізація явного nullвимагає набагато більше зусиль, тому що ви повинні перевірити, чи ненульова посилання насправді встановлена ​​правильно після ініціалізації.

Усі інші мови такі ж, як і Java. Зазвичай вони копіюють те, як це робить C ++ або Java, і враховуючи, наскільки основна концепція неявних nullпосилальних типів, стає дуже важко спроектувати мову, яка використовує явну мову null.


"Вони, мабуть, не хотіли возитися з такою основною концепцією мови". Вони вже повністю видалили покажчики, і null, здається , видалення не буде настільки великим зміною.
svick

2
@svick Для Java посилання є заміною покажчиків. І в багатьох випадках покажчики на C ++ використовувались так само, як і посилання на Java. Деякі люди навіть стверджують, що у Java є вказівники ( programmers.stackexchange.com/questions/207196/… )
Euphoric

Я позначив це правильною відповіддю. Я хотів би підтримати це, але у мене недостатньо репутації.
aochagavia

1
Погодьтеся. Зауважте, що в C ++ (на відміну від Java та C) виняток з можливістю пропустити. std::stringбути не може null. int&не може бути null. Кан int*і C ++ дозволяють неперевірений доступ до нього з двох причин: 1. тому що C зробив і 2. тому що ви повинні зрозуміти, що ви робите, використовуючи вказівники в C ++.
MSalters

@MSalters: Якщо тип не має бітового значення, яке можна скопіювати за замовчуванням, то для створення масиву цього типу потрібно буде викликати конструктор для кожного його елемента, перш ніж дозволити доступ до самого масиву. Це може зажадати марної роботи (якщо деякі чи всі елементи будуть перезаписані до того, як вони будуть прочитані), це може спричинити ускладнення, якщо конструктор для більш пізнього елемента вийде з ладу після того, як був побудований більш ранній елемент, і в кінцевому підсумку все-таки не вдасться зробити багато чого (якщо належне значення для деяких елементів масиву неможливо було визначити, не читаючи інших).
supercat

15

Власне, nullце чудова ідея. Давши вказівник, ми хочемо вказати, що цей покажчик не посилається на дійсне значення. Отже, ми беремо одне місце пам’яті, оголошуємо його недійсним і дотримуємось цієї конвенції (конвенції, яка інколи застосовується з segfault). Тепер, коли у мене є вказівник, я можу перевірити, чи містить він Nothing( ptr == null) або Some(value)( ptr != null, value = *ptr). Я хочу, щоб ви зрозуміли, що це рівнозначно Maybeтипу.

Проблеми з цим полягають у:

  1. У багатьох мовах система типу тут не допомагає гарантувати ненулеву посилання.

    Це історичний багаж, оскільки багато основних імперативних мов чи мов OOP мали лише поступовий прогрес у своїх системах типу порівняно з попередниками. Невеликі зміни мають перевагу в тому, що нові мови легше вивчати. C # - основна мова, яка впровадила інструменти на рівні мови, щоб краще обробляти нулі.

  2. Дизайнери API можуть повернутись nullна невдачу, але не бути посиланням на реальну саму справу про успіх. Часто річ (без посилання) повертається безпосередньо. Таке вирівнювання одного рівня вказівника унеможливлює використання nullяк значення.

    Це лише лінь з боку дизайнера, і не можна допомогти без застосування належного типу типу. Деякі люди також можуть спробувати виправдати це міркуваннями щодо продуктивності або наявністю необов'язкових перевірок (колекція може повернутись nullабо сам предмет, але також запропонувати containsметод).

  3. У Хаскелл чіткий вид на Maybeтип як монада. Це полегшує складання перетворень на значення, що міститься.

    З іншого боку, мови низького рівня, такі як C, ледь не розглядають масиви як окремий тип, тому я не впевнений, що ми очікуємо. У мовах OOP з параметризованим поліморфізмом Maybeтип перевіреного виконання досить тривіальний для реалізації.


"C # робить кроки від нульових посилань". Які кроки це? Я нічого не бачив у змінах мови. Або ви маєте на увазі лише те, що загальні бібліотеки використовують nullменше, ніж раніше?
svick

@svick Погане словосполучення з мого боку. " C # - це основна мова, яка впровадила інструменти на рівні мови для кращого управління nulls " - я говорю про Nullableтипи та ??оператор за замовчуванням. Це не вирішує проблему прямо зараз в присутності успадкованого коду, але це є кроком на шляху до кращого майбутнього.
амон

Я згоден з вами. Я проголосував би за вашу відповідь, але не маю доброї репутації: (Однак, Nullable працює лише для примітивних типів. Отже, це лише невеликий крок.
aochagavia

1
@svick Nullable нічого спільного з цим не має. Ми говоримо про всі типи посилань, які явно дозволяють нульове значення, замість того, щоб програміст чітко визначав це. І Nullable можна використовувати лише для типів значень.
Ейфорія

@Euphoric Я думаю, що ваш коментар мав на увазі як відповідь на Амон, я не згадував Nullable.
svick

9

Я розумію, що це nullбула необхідна конструкція для того, щоб абстрактні мови програмування вийшли з монтажу. 1 Програмістам потрібна можливість вказати на те, що значення вказівника або регістру було not a valid valueі nullстало загальним терміном для цього значення.

Підсилюючи точку, яка nullє лише умовою для представлення концепції, фактичне значення, яке було nullвикористано для того, щоб мати можливість / може змінюватись залежно від мови програмування та платформи.

Якщо ви розробляли нову мову і хотіли уникати, nullале maybeзамість цього використовувати , тоді я закликаю більш описовий термін, наприклад, not a valid valueабо navvвказувати намір. Але назва цієї нецінної цінності - це окреме поняття від того, чи слід дозволити, щоб нецінні значення існували навіть у вашій мові.

Перш ніж приймати рішення щодо будь-якого з цих двох моментів, вам потрібно визначити, що maybeозначатиме значення для вашої системи. Ви можете виявити, що це просто перейменування nullсенсу, not a valid valueабо ви можете виявити, що воно має іншу семантичну для вашої мови.

Так само рішення про те, чи потрібно перевіряти доступ на nullдоступ чи посилання, є ще одним дизайнерським рішенням вашої мови.

Щоб дати трохи історії, Cбуло неявне припущення, що програмісти розуміють, що вони намагаються зробити під час маніпулювання пам'яттю. Оскільки це було чудовим абстрагуванням на зборах та необхідними мовами, які передували йому, я б ризикнув, що думка про захист програміста від невірного посилання не натрапила на їх думку.

Я вважаю, що деякі компілятори або їх додаткові інструменти можуть забезпечити міру перевірки недійсного доступу вказівника. Тож інші відзначили це потенційне питання та вжили заходів для його захисту.

Ви можете дозволити це чи ні, це залежить від того, що ви хочете, щоб ваша мова була виконана і який рівень відповідальності ви хочете покласти на користувачів вашої мови. Це також залежить від вашої здатності створити компілятор, щоб обмежити такий тип поведінки.

Отже, щоб відповісти на ваші запитання:

  1. "Що за аргументи ..." - Ну, це залежить від того, що ви хочете зробити мовою. Якщо ви хочете імітувати доступ з голого металу, то ви можете дозволити це.

  2. "це просто історичний багаж?" Можливо, можливо, ні. nullбезумовно, має / має значення для ряду мов і допомагає керувати вираженням цих мов. Історичний прецедент, можливо, вплинув на новіші мови та їхнє дозволення, nullале трохи махнути руками та оголосити концепцію марною історичною поклажею.


1 Дивіться цю статтю у Вікіпедії, хоча Хоару призначається нульові значення та об'єктно-орієнтовані мови. Я вважаю, що імперативні мови прогресували по іншому сімейному дереву, ніж Альгол.


Справа в тому, що більшості змінних у, скажімо, C # або Java, може бути призначена нульова посилання. Схоже, було б набагато краще призначати нульові посилання лише об’єктам, які прямо вказують на те, що "Можливо" немає посилання. Отже, моє запитання стосується нуля "поняття", а не слова.
aochagavia

2
"Нульові посилання вказівника можуть відображатися як помилки під час компіляції" Які компілятори можуть це зробити?
svick

Щоб бути абсолютно справедливим, ви ніколи не присвоюєте нульову посилання на об’єкт ... посилання на об'єкт просто не існує (опорні точки нічого (0x000000000), що за визначенням null).
mgw854

Цитата специфікації C99 говорить про нульовий символ , а не про нульовий покажчик , це два дуже різні поняття.
svick

2
@ GlenH7 Це не для мене. Код object o = null; o.ToString();для мене складеться чудово, без помилок чи попереджень у VS2012. ReSharper на це скаржиться, але це не компілятор.
svick

7

Якщо ви подивитеся на приклади цитованої статті, більшість випадків використання Maybeне скорочує код. Це не усуває необхідності перевірки Nothing. Єдина відмінність полягає в тому, що це нагадує вам зробити це через тип системи.

Зауважте, я кажу "нагадай", а не сила. Програмісти ліниві. Якщо програміст переконаний, що значення можливо не може бути Nothing, вони збираються знеструмлювати його, Maybeне перевіряючи його, так само, як вони перенаправляють нульовий покажчик зараз. Кінцевим результатом є перетворення виключення нульового вказівника у виняток "dereferenced empty, можливо".

Цей же принцип людської природи застосовується і в інших сферах, де мови програмування намагаються змусити програмістів щось робити. Наприклад, дизайнери Java намагалися змусити людей обробляти більшість винятків, що призвело до безлічі котлів, які або мовчки ігнорують, або сліпо поширюють винятки.

Що Maybeприємно, це те, що багато рішень приймаються за допомогою узгодження моделей та поліморфізму замість явних перевірок. Наприклад, можна створити окремі функції processData(Some<T>)і processData(Nothing<T>), що ви не можете зробити з null. Ви автоматично переміщуєте обробку помилок на окрему функцію, що дуже бажано у функціональному програмуванні, коли функції передаються навколо та оцінюються ліниво, а не завжди викликаються зверху вниз. У OOP кращий спосіб декупажу коду обробки помилок - це винятки.


Як ви вважаєте, це було б бажаною особливістю нової мови ОО?
aochagavia

Ви можете реалізувати це самостійно, якщо хочете отримати переваги поліморфізму. Єдине, для чого вам потрібна підтримка мови, - це не зводити нанівець. Я хотів би побачити спосіб вказати для себе подібний const, але зробити це необов’язковим. Деякі види коду нижчого рівня, як, наприклад, пов'язані списки, було б дуже прикро реалізовувати з об'єктами, що не зведені нанівець.
Карл Білефельдт

2
Не потрібно перевіряти тип "Можливо". Тип "Можливо" - це монада, тому вона повинна мати функції map :: Maybe a -> (a -> b) та bind :: Maybe a -> (a -> Maybe b)визначені на ній, тож ви можете продовжувати та ниткати подальші обчислення здебільшого за допомогою переформатування, використовуючи оператор if else. І getValueOrDefault :: Maybe a -> (() -> a) -> aдозволяє обробляти нульовий корпус. Це набагато елегантніше, ніж Maybe aчітке узгодження візерунка .
DetriusXii

1

Maybeце дуже функціональний спосіб мислення проблеми - є річ, і вона може мати або не мати значення, яке визначене. Однак в об'єктно-орієнтованому сенсі ми замінюємо цю ідею речі (незалежно від того, має вона значення чи ні) об'єктом. Ясна річ, що об’єкт має значення. Якщо це не так, ми говоримо, що об'єкт є null, але те, що ми насправді маємо на увазі, це те, що його немає взагалі. Посилання, яке ми маємо на об’єкт, не вказує ні на що. Переклад Maybeна концепцію ОО нічого не робить новим - насправді це просто створює більш суцільний код. Ви все ще повинні мати нульову посилання на значенняMaybe<T>. Вам все одно доведеться робити нульові перевірки (насправді, вам доведеться робити набагато більше перевірених на нуль, захаращуючи свій код), навіть якщо вони зараз називаються "можливо чеками". Звичайно, ви напишете більш надійний код, як стверджує автор, але я стверджую, що це лише так, оскільки ви зробили мову набагато абстрактнішою і тупішою, вимагаючи рівня роботи, який у більшості випадків не потрібний. Я охоче прийматимуть NullReferenceException раз у раз, ніж мати справу зі кодом спагетті, роблячи Можливо перевіряти кожен раз, коли я отримую доступ до нової змінної.


2
Я думаю, що це призведе до меншої кількості нульових перевірок, тому що вам потрібно лише перевірити, чи ви бачите, можливо, <T> і не потрібно турбуватися про інші типи.
aochagavia

1
@svick Maybe<T>має дозволити nullяк значення, оскільки значення може не існувати. Якщо у мене є Maybe<MyClass>, і воно не має значення, поле значення повинно містити нульове посилання. Немає нічого іншого, щоб це було достовірно безпечно.
mgw854

1
@ mgw854 Звичайно, є. У мові ОО Maybeможе бути абстрактний клас з двома класами, які успадковують його: Some(у якого є поле для значення) та None(у якого немає цього поля). Таким чином, значення ніколи не буває null.
svick

6
За замовчуванням "not-nulability" і, можливо, <T> ви можете бути впевнені, що деякі змінні завжди містять об'єкти. А саме всі змінні, які не є, можливо, <T>
aochagavia

3
@ mgw854 Точкою цієї зміни буде підвищення виразної сили для розробника. Зараз розробник завжди повинен припускати, що посилання або вказівник можуть бути недійсними, і потрібно перевірити, чи є корисне значення. Реалізуючи цю зміну, ви даєте повноваження розробника сказати, що він дійсно потребує дійсного значення, і перевірити компілятор, щоб переконатися, що дійсне значення було передано. Але все ж дайте розробнику можливість увімкнути та не передати значення.
Ейфорія

1

Концепцію nullможна легко простежити до C, але в цьому не проблема.

Моєю щоденною мовою вибору є C #, і я б дотримувався nullоднієї різниці. C # має два типи, значення та посилання . Значень ніколи не може бути null, але я хотів би мати можливість виразити, що жодне значення не є ідеальним. Для цього C # використовує Nullableтипи, таким чином int, буде значення та int?нульове значення. Ось так я думаю, що також повинні працювати посилання на типи.

Також дивіться: Нульова посилання може бути помилкою :

Нульові посилання корисні, а іноді й незамінні (врахуйте, скільки проблем, якщо ви можете чи не можете повернути рядок у C ++). Помилка насправді не в існуванні нульових покажчиків, а в тому, як система типів обробляє їх. На жаль, більшість мов (C ++, Java, C #) неправильно обробляють їх.


0

Я думаю, це тому, що функціональне програмування сильно хвилює типи , особливо типи, що поєднують інші типи (кортежі, функції як типи першого класу, монади тощо), ніж це робиться (або принаймні спочатку).

Сучасні версії мов програмування, я думаю, про які ви говорите (C ++, C #, Java), базуються на мовах, які не мали жодної форми загального програмування (C, C # 1.0, Java 1). Без цього ви все-таки зможете визначити якусь різницю між зведеними та ненульовими об'єктами в мові (як, наприклад, посилання на C ++, які не можуть бути null, але також обмежені), але це набагато менш природно.


Я думаю, що у випадку функціонального програмування, це випадки, коли FP не має посилальних чи вказівних типів. У FP все - цінність. І якщо у вас є тип вказівника, легко сказати «покажчик на ніщо».
Ейфорія

0

Я думаю, що основна причина полягає в тому, що для забезпечення програми "безпечною" від корупції даних потрібно відносно небагато недійсних перевірок. Якщо програма намагається використати вміст елемента масиву чи інше місце зберігання, яке, як передбачається, було написано з дійсною посиланням, але не було, найкращий результат - це викид для викиду. В ідеалі виняток точно вказуватиме місце виникнення проблеми, але важливо, що якийсь виняток буде викинутий до того, як нульова посилання зберігається десь, що може спричинити пошкодження даних. Якщо метод не зберігає об'єкт, не намагаючись спочатку його використовувати якось, спроба використання об'єкта сама по собі буде являти собою "нульову перевірку".

Якщо ви хочете переконатися, що нульова посилання, яка з’являється там, де вона не повинна, спричиняє певний виняток, окрім іншого NullReferenceException, часто доведеться включати нульові перевірки всюди. З іншого боку, просто гарантувати, що якийсь виняток відбудеться до того, як нульова посилання може спричинити "збитки", поза тим, що вже було зроблено, часто вимагає порівняно мало тестів - тестування, як правило, вимагається лише у випадках, коли об'єкт зберігає посилання, не намагаючись використовувати його, і або нульова посилання замінить дійсну, або призведе до неправильного тлумачення інших аспектів стану програми. Такі ситуації існують, але чи не все так часто; більшість випадкових нульових посилань потраплять дуже швидкочи перевіряє їх чи ні .


цю публікацію досить важко читати (стіна тексту). Ви б не хотіли відредагувати його в кращій формі?
гнат

1
@gnat: Це краще?
supercat

0

" Maybe," як написано, є конструкцією вищого рівня, ніж нульовою. Можливо, використовуючи більше слів для його визначення, можливо, це "вказівник або на річ, або вказівник на ніщо, але компілятору ще не було надано достатньо інформації для визначення того, яке саме". Це змушує вас постійно чітко перевіряти кожне значення, якщо тільки ви не побудуєте специфікацію компілятора, яка є достатньо розумною, щоб не відставати від написаного вами коду.

Ви можете виконати Можливо з мовою, яка має нулі легко. C ++ має його у формі boost::optional<T>. Зробити еквівалент нуля за допомогою Можливо дуже складно. Зокрема, якщо у мене є Maybe<Just<T>>, я не можу призначити його null (тому що таке поняття не існує), тоді як T**в мові з null дуже просто призначити null. Це змушує використовувати Maybe<Maybe<T>>, що цілком справедливо, але змусить вас зробити ще багато перевірок, щоб використовувати цей об’єкт.

Деякі функціональні мови використовують Можливо тому, що null вимагає або невизначеного поведінки, або обробку винятків, жодна з яких не є простою концепцією для зіставлення функціональних синтаксисів мови. Можливо, це набагато краще заповнює роль у таких функціональних ситуаціях, але в процедурних мовах нульове значення є корольним. Це не питання правильного і неправильного, а питання того, що полегшує сказати комп’ютеру робити те, що ви хочете.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.