Чи має сенс мати і поняття "null", і "можливо"?


11

Створюючи клієнта для веб-API в C #, я зіткнувся з проблемою, nullякою він вважав би дві різні речі:

  • нічого , наприклад, fooможе бути або не матиbar
  • невідомо : за замовчуванням відповідь API включає лише підмножину властивостей, ви повинні вказати, які додаткові властивості ви хочете. Так невідомо означає, що власність не була запитана в API.

Після деяких пошуків я дізнався про тип Можливо (або Варіант), як він використовується у функціональних мовах та як він «вирішує» нульові проблеми з перенаправленням, змушуючи користувача задуматися про можливу відсутність значення. Однак усі ресурси, з якими я стикався, говорили про заміну нуля на Можливо . Я виявив деякі згадки про тризначну логіку , але я не цілком її розумію, і більшість випадків її згадування відбувалося в контексті "це погано".

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


9
Це не має сенсу мати null. Це ретельно зламана ідея.
Андрій Бауер

7
Не робіть, можливо, може, foo, який має іншу семантику, ніж може бути foo. Можливо, це монада , і один із законів монади полягає в тому, що M M xі M xповинен мати однакову семантику.
Ерік Ліпперт

2
Ви можете розглянути дизайн ранніх версій Visual Basic, в яких нічого не було (посилання на жоден об'єкт), Null (нульова семантика бази даних), Empty (змінна не ініціалізована) та Missing (необов'язковий параметр не був переданий). Ця конструкція була складною і багато в чому непослідовною, але є причина, чому ці концепції не були пов'язані між собою.
Ерік Ліпперт

3
Так, я б не використовував, можливо, і те, і інше. Але насправді так Maybe aсамо, як , семантично, те саме, що , і ізоморфне для введення з відповіді @Andej. Ви також можете визначити свій власний екземпляр монади для цього типу, і тому використовувати різні комбінатори монади. a + 1 + 1a+1Maybe Maybe aa+1+1UserInput a
Євге

12
@EricLippert: помилково, що " M (M x)і M xмає бути однакова семантика". Візьмемо M = Listдля прикладу: списки списків не те саме, що списки. Коли Mмонада, є перетворення (а саме монада множення) від M (M x)до M xякої пояснює відносини між ними, але вони не мають «таку ж семантику».
Андрій Бауер

Відповіді:


14

nullЗначення в даний час за замовчуванням всюди тільки дійсно зламана ідея , так що забудьте про це.

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

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

data UnknownNothing a =
     Unknown
   | Nothing
   | Value a

Можливо навіть, ви повинні використовувати щось більш описове, яке простіше запам’ятати:

data UserInput a =
     Unknown
   | NotGiven
   | Answer a

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


Має ідеальний сенс, коли ви бачите, що це написано так. Мене найбільше цікавлять ідеї, існуюча підтримка бібліотеки - це лише бонус :)
Stijn

Якби тільки бар’єр для створення таких типів був нижчим у багатьох мовах. : /
Рафаель

Якби тільки люди перестали користуватися мовами 1960-х (або їх перевтіленнями з 1980-х).
Андрій Бауер

@AndrejBauer: що? але тоді теоретикам не було б на що скаржитися!
Yttrill

Ми не скаржимось, ми далеко.
Андрій Бауер

4

Наскільки я знаю, значення nullв C # є можливим значенням для деяких змінних, залежно від його типу (я правий?). Наприклад, екземпляри якогось класу. Для решти типів (наприклад int, boolтощо) ви можете додати це значення виключення, оголосивши змінні за допомогою int?або bool?замість цього (саме це Maybeробить конструктор, як я опишу далі).

Конструктор Maybeтипів з функціонального програмування додає це нове значення виключення для даного типу даних. Отже, якщо Intце тип цілих чисел або Gameтип стану гри, то Maybe Intмає всі цілі числа плюс нульове (іноді зване нічого ) значення. Те саме для Maybe Game. Тут немає типів, які мають nullзначення. Ви додаєте його, коли вам це потрібно.

ІМО, цей останній підхід є кращим для будь-якої мови програмування.


Так, ваше розуміння C # правильне. Тож чи можу я зробити з вашої відповіді, що ви пропонуєте гніздування Maybeі nullповністю скинути ?
Штійн

2
Моя думка - про те, якою має бути мова. Я не знаю найкращих практик програмування на C #. У вашому випадку найкращим варіантом було б визначення типу даних, як описав @Andrej Bauer, але я думаю, що ви не можете цього зробити в C #.
Євге

@Euge: Типи алгебраїчних сум можна (приблизно) наблизити до класичного підтипу в стилі OO та підтипу поліморфного повідомлення. Ось так це робиться, наприклад, у Scala, з додатковим поворотом, щоб забезпечити закриті типи. Тип стає абстрактним надкласом, конструктори типів стають конкретними підкласами, відповідність шаблонів над конструкторами може бути апроксимована або шляхом переміщення випадків у перевантажені методи в конкретних підкласах або isinstanceтестів. У Scala також є "правильне" узгодження шаблонів, і C♯ насправді недавно набув простих шаблонів.
Йорг W Міттаг

"Додатковий поворот", який я згадував для Scala, полягає в тому, що крім "стандартних" модифікаторів "віртуальний" (для класу, який може бути успадкований від) та final(для класу, від якого не можна успадкувати), він також має sealedдля клас, який можна розширити лише в межах однієї компіляційної одиниці . Це означає, що компілятор може статично знати всі можливі підкласи (за умови, що всі вони або самі, sealedабо final), і таким чином робити вичерпну перевірку відповідності шаблону, що неможливо статично для мови з завантаженням коду виконання та необмеженим успадкуванням.
Йорг W Міттаг

Звичайно, ви можете спроектувати типи з цією семантикою в C #. Зауважте, що C # не дозволяє вбудовувати тип вбудованого типу (який називається Nullable у C #). int??є незаконним у C #.
Ерік Ліпперт

3

Якщо ви можете визначити тип об'єднання, як X or Y, і якщо у вас є тип імені , Nullякий представляє тільки nullзначення (і нічого більше), то для будь-якого типу T, T or Nullнасправді не так вже відрізняється від Maybe T. Єдина проблема nullполягає в тому, коли мова трактує тип Tяк T or Nullнеявно, вносячи nullдійсне значення для кожного типу (за винятком примітивних типів у мовах, які їх мають). Це відбувається, наприклад, на Java, де функція, яка приймає Stringтакож, приймає null.

Якщо ти розглядаєш типи як набір значень і orяк об'єднання множин, то (T or Null) or Nullпредставляє множину значень T ∪ {null} ∪ {null}, що те саме, що T or Null. Є компілятори, які виконують подібний аналіз (SBCL). Як сказано в коментарях, ви не можете легко розрізнити типи Maybe Tта Maybe Maybe Tпереглядати їх як домени (з іншого боку, цей погляд є досить корисним для програм, що динамічно набираються). Але ви також можете зберегти типи як алгебраїчні вирази, де (T or Null) or Nullзображено кілька рівнів Maybes.


2
Власне, важливо, що Maybe (Maybe X)це не те саме, що Maybe X. Nothingце не те саме, що Just Nothing. Зіткнення Maybe (Maybe X)з Maybe Xнеможливим лікувати Maybe _поліморфно.
Жил "ТАК - перестань бути злим"

1

Вам потрібно з’ясувати, що ви хочете висловити, а потім знайти спосіб, як це висловити.

По-перше, у вашому проблемному домені є значення (наприклад, цифри, рядки, записи клієнтів, булеві і т.д.). По-друге, у вас є додаткові, загальні значення поверх значень проблемних доменів: Наприклад, "нічого" (відома відсутність значення), "невідомо" (відсутність знань про наявність чи відсутність значення), не згадане " щось "(певна цінність є, але ми не знаємо, яка). Можливо, ви можете придумати інші ситуації.

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

У деяких мовах деякі випадки простіші. Наприклад, у Swift у вас є для кожного типу T тип "необов'язковий T", який має як можливі значення нуль плюс усі значення T. Це дозволяє легко обробляти багато ситуацій, і це ідеально, якщо у вас є "нічого" і " значення ". Ви можете використовувати його, якщо у вас є "невідоме" та "значення". Ви не можете його використовувати, якщо вам потрібно обробити "нічого", "невідомо" та "значення". Інші мови можуть використовувати "null" або "можливо", але це просто інше слово.

У JSON у вас є словники з парами ключ / значення. Тут ключ може просто відсутній у словнику, або він може мати значення null. Отже, ретельно описуючи, як зберігаються речі, ви можете представити загальні значення плюс два додаткові значення.

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