Имеет ли смысл иметь понятия «ноль» и «возможно»?

11

При создании клиента для веб-API в C # я столкнулся с проблемой, связанной nullсо значением, в которой он представлял бы две разные вещи:

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

После некоторых поисков я узнал о типе Maybe (или Option), о том, как он используется в функциональных языках и как он «решает» проблемы нулевой разыменования, заставляя пользователя задуматься о возможном отсутствии значения. Тем не менее, все ресурсы, с которыми я столкнулся, говорили о замене null на Maybe . Я нашел некоторые упоминания о трехзначной логике , но я не до конца понимаю ее, и в большинстве случаев ее упоминание было в контексте «это плохо».

Теперь мне интересно, имеет ли смысл иметь как концепцию null, так и Maybe , чтобы представлять неизвестное и ничего соответственно. Это трехзначная логика, о которой я читал, или у нее другое имя? Или это намеченный способ вложить «Возможно в»?

Стейн
источник
9
Это не делает sanse иметь null. Это полностью сломанная идея.
Андрей Бауэр
7
Не делайте «возможно, может быть, фу», у которого семантика отличается от «возможно, фу». Может быть , это монада , и один из законов монад является то , что M M xи M xдолжны иметь ту же семантику.
Эрик Липперт
2
Возможно, вы захотите взглянуть на дизайн ранних версий Visual Basic, в котором было Nothing (ссылка на отсутствие объекта), Null (нулевая семантика базы данных), Empty (переменная не инициализирована) и Missing (необязательный параметр не был передан). Этот дизайн был сложным и во многом противоречивым, но есть причина, по которой эти понятия не были связаны друг с другом.
Эрик Липперт
3
Да, я бы тоже не стал пользоваться. Но на самом деле, Maybe aто же самое, что , семантически, то же самое, что , и изоморфно типу из ответа @Andej. Для этого типа вы также можете определить свой собственный экземпляр монады и, следовательно, использовать разные комбинаторы монад. + 1 + 1a+1Maybe Maybe aa+1+1UserInput a
Euge
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-х).
Андрей Bauer
@AndrejBauer: что? но тогда теоретикам не на что было бы жаловаться!
Yttrill
Мы не жалуемся, мы в воздухе.
Андрей Bauer
4

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

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

ИМО, этот последний подход лучше для любого языка программирования.

Euge
источник
Да, ваше понимание C # является правильным. Так могу ли я сделать вывод из вашего ответа, что вы предлагаете вложить Maybes и nullполностью отбросить ?
Стейн
2
Мое мнение о том, каким должен быть язык. Я действительно не знаю лучших практик программирования на C #. В вашем случае лучшим вариантом будет определение типа данных, как описал @Andrej Bauer, но я думаю, что вы не можете сделать это в C #.
Euge
@Euge: Типы алгебраической суммы может быть (примерно) аппроксимируется с классическими подтипами ОО-стиля и подтип полиморфных отправленным сообщения. Вот как это делается в Scala, например, с дополнительным завихрением , чтобы для закрытых типов. Тип становится абстрактным суперклассом, конструкторы типов стали конкретными подклассами, сопоставление с образцом над конструкторами может быть аппроксимировано либо путем перемещения дел в перегруженные методы в конкретных подклассах или isinstanceиспытаниях. Scala также имеет «правильное» сопоставление с образцом, и C♯ фактически недавно также получил простые шаблоны.
Йорг W Mittag
«Дополнительный поворот», который я упомянул для Scala, заключается в том, что в дополнение к «стандартным» модификаторам «virtual» (для класса, который может быть унаследован от) и final(для класса, который не может быть унаследован от), он также имеет sealedдля класс, который может быть расширен только внутри одной и той же единицы компиляции . Это означает, что компилятор может статически знать все возможные подклассы (при условии, что все они либо сами, sealedлибо final) и, таким образом, выполнять исчерпывающие проверки на соответствие шаблонам, что статически невозможно для языка с загрузкой кода времени выполнения и неограниченным наследованием.
Йорг Миттаг
Конечно, вы можете создавать типы с этой семантикой в ​​C #. Обратите внимание, что C # не допускает вложенности встроенного типа Maybe (называемого 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.

CoreDump
источник
2
На самом деле, важно, что Maybe (Maybe X)это не то же самое, что Maybe X. Nothingэто не то же самое, что Just Nothing. Сопоставление Maybe (Maybe X)с ним Maybe Xделает невозможным Maybe _полиморфное лечение .
Жиль "ТАК - перестань быть злым"
1

Вам нужно выяснить, что вы хотите выразить, а затем найти способ, как выразить это.

Во-первых, у вас есть значения в проблемной области (например, числа, строки, записи клиентов, логические значения и т. Д.). Во-вторых, у вас есть несколько дополнительных общих значений поверх значений проблемной области: например, «ничего» (известное отсутствие значения), «неизвестно» (нет сведений о наличии или отсутствии значения), не упоминалось «» что-то "(определенно есть значение, но мы не знаем, какое именно). Может быть, вы можете думать о других ситуациях.

Если вы хотите иметь возможность представлять все значения плюс эти три дополнительных значения, вы должны создать перечисление со словами «ничто», «неизвестно», «что-то» и «значение» и перейти оттуда.

В некоторых языках некоторые случаи проще. Например, в Swift у вас есть для каждого типа T тип «необязательный T», который имеет в качестве возможных значений nil плюс все значения T. Это позволяет вам легко справляться со многими ситуациями, и идеально, если у вас есть «ничто» и « значение". Вы можете использовать его, если у вас есть «неизвестно» и «значение». Вы не можете использовать его, если вам нужно обрабатывать «ничего», «неизвестно» и «значение». Другие языки могут использовать «ноль» или «возможно», но это просто другое слово.

В JSON у вас есть словари с парами ключ / значение. Здесь ключ может просто отсутствовать в словаре или может иметь значение null. Таким образом, тщательно описав, как вещи хранятся, вы можете представить общие значения плюс два дополнительных значения.

gnasher729
источник