Время от времени, когда программисты жалуются на нулевые ошибки / исключения, кто-то спрашивает, что мы делаем без нуля.
У меня есть некоторое базовое представление о крутизне типов опций, но я не обладаю знаниями или языковыми навыками, чтобы лучше всего это выразить. Что является отличным объяснением следующего, написанного так, чтобы это было доступно обычному программисту, на которого мы могли бы указать этому человеку?
- Нежелательно иметь ссылки / указатели быть обнуляемыми по умолчанию
- Как работают типы опций, включая стратегии для облегчения проверки нулевых случаев, таких как
- сопоставление с образцом и
- монадические понимания
- Альтернативное решение, такое как сообщение есть ноль
- (другие аспекты, которые я пропустил)
Ответы:
Я думаю, что краткое резюме того, почему ноль нежелателен, состоит в том, что бессмысленные состояния не должны быть представимыми .
Предположим, я моделирую дверь. Он может находиться в одном из трех состояний: открыто, закрыто, но разблокировано, закрыто и заблокировано. Теперь я мог бы смоделировать его в соответствии с
и ясно, как отобразить мои три состояния в эти две логические переменные. Но это оставляет четвертое, нежелательное состояние доступно:
isShut==false && isLocked==true
. Поскольку типы, которые я выбрал в качестве своего представления, допускают это состояние, я должен приложить умственные усилия, чтобы гарантировать, что класс никогда не попадет в это состояние (возможно, путем явного кодирования инварианта). Напротив, если бы я использовал язык с алгебраическими типами данных или проверенные перечисления, которые позволяют мне определитьтогда я мог бы определить
и больше нет забот. Система типов гарантирует, что существует только три возможных состояния для экземпляра
class Door
. Это то, к чему хорошо подходят системы типов - явное исключение целого класса ошибок во время компиляции.Проблема в
null
том, что каждый ссылочный тип получает это дополнительное состояние в своем пространстве, которое обычно нежелательно.string
Переменная может быть любая последовательность символов, или это может быть этот сумасшедший дополнительноеnull
значение , которое не отображает в моей проблемной области. УTriangle
объекта есть триPoint
s, у которых естьX
иY
значения, и , к сожалению, самиPoint
поTriangle
себе или s могут быть этим сумасшедшим нулевым значением, которое не имеет смысла для графической области, в которой я работаю. И т.д.Если вы намереваетесь смоделировать потенциально несуществующее значение, вы должны явно указать его. Если я собираюсь смоделировать людей так, чтобы у каждого
Person
были aFirstName
и aLastName
, но только у некоторых людей естьMiddleName
s, то я хотел бы сказать что-то вродегде
string
здесь предполагается ненулевой тип. Тогда нет никаких хитрых инвариантов для установления и никаких неожиданныхNullReferenceException
s при попытке вычислить длину чьего-либо имени. Система типов гарантирует, что любой код, имеющий дело сMiddleName
учетными записями, может быть такимNone
, а любой код, имеющий дело с этим,FirstName
может с уверенностью предположить, что там есть значение.Например, используя приведенный выше тип, мы могли бы написать эту глупую функцию:
без забот. Напротив, в языке с обнуляемыми ссылками для таких типов, как строка, тогда предполагается
в конечном итоге вы создаете такие вещи, как
который взрывается, если входящий объект Person не имеет инварианта всего, что является ненулевым, или
или, может быть
при условии, что это
p
гарантирует наличие первого / последнего, но середина может быть нулевой, или, может быть, вы делаете проверки, которые выдают различные типы исключений, или кто знает что. Все эти сумасшедшие варианты реализации и вещи, о которых нужно подумать, возникают, потому что есть эта глупая представимая ценность, которая вам не нужна или не нужна.Нуль обычно добавляет ненужную сложность. Сложность - враг всего программного обеспечения, и вы должны стремиться уменьшать сложность всякий раз, когда это целесообразно.
(Обратите внимание, что даже эти простые примеры сложнее. Даже если a
FirstName
не может бытьnull
, astring
может представлять""
(пустую строку), что, вероятно, также не является личным именем, которое мы намереваемся смоделировать. Таким образом, даже с не обнуляемые строки, все равно может случиться так, что мы «представляем бессмысленные значения». Опять же, вы можете решить бороться с этим либо с помощью инвариантов и условного кода во время выполнения, либо с помощью системы типов (например, чтобы иметьNonEmptyString
тип). последний, вероятно, опрометчив («хорошие» типы часто «закрываются» по набору общих операций и, напримерNonEmptyString
, не закрываются по.SubString(0,0)
), но это демонстрирует больше точек в пространстве дизайна. В конце концов, в любой системе типов есть некоторая сложность, от которой будет очень хорошо избавиться, и другая сложность, от которой просто сложнее избавиться. Ключевым моментом в этом разделе является то, что почти во всех системах типов изменение с «необнуляемых ссылок по умолчанию» на «необнуляемые ссылки по умолчанию» почти всегда является простым изменением, которое делает систему типов намного лучше в борьбе со сложностью и исключение определенных типов ошибок и бессмысленных состояний. Поэтому довольно безумно, что многие языки повторяют эту ошибку снова и снова.)источник
Хорошая вещь о типах опций не в том, что они необязательны. Это то, что все другие типы не являются .
Иногда нам нужно представлять какое-то «нулевое» состояние. Иногда мы должны представлять опцию «без значения», а также другие возможные значения, которые может принимать переменная. Таким образом, язык, который категорически запрещает это, будет немного искажен.
Но часто нам это не нужно, и разрешение такого «нулевого» состояния приводит только к двусмысленности и путанице: каждый раз, когда я обращаюсь к переменной ссылочного типа в .NET, я должен учитывать, что она может быть нулевой .
Часто он никогда не будет нулевым, потому что программист структурирует код так, что это никогда не произойдет. Но компилятор не может это проверить, и каждый раз, когда вы его видите, вы должны спросить себя: «Может ли это быть нулевым? Нужно ли здесь проверять нулевое?»
В идеале, во многих случаях, когда ноль не имеет смысла, его нельзя допускать .
Это сложно достичь в .NET, где почти все может быть нулевым. Вы должны положиться на автора кода, который вы называете, чтобы он был на 100% дисциплинированным и последовательным и четко задокументировал, что может и не может быть нулевым, или вы должны быть параноиком и проверять все .
Однако, если типы по умолчанию не обнуляются , вам не нужно проверять, являются ли они пустыми. Вы знаете, что они никогда не могут быть нулевыми, потому что компилятор / средство проверки типов навязывает это вам.
И тогда нам нужны просто задняя дверь для тех редких случаев , когда мы делаем необходимость обрабатывать состояние нуля. Затем можно использовать тип «option». Затем мы допускаем нулевое значение в тех случаях, когда мы сознательно приняли решение о том, что нам необходимо представлять случай «без значения», а во всех остальных случаях мы знаем, что значение никогда не будет нулевым.
Как уже упоминалось, в C # или Java, например, нуль может означать одно из двух:
Второе значение должно быть сохранено, но первое должно быть полностью исключено. И даже второе значение не должно быть значением по умолчанию. Это то, что мы можем выбрать, если и когда нам это нужно . Но когда нам не нужно что-то необязательное, мы хотим, чтобы средство проверки типов гарантировало, что оно никогда не будет нулевым.
источник
Все ответы до сих пор фокусируются на том, почему
null
это плохо, и как это удобно, если язык может гарантировать, что определенные значения никогда не будут нулевыми.Затем они продолжают предполагать, что было бы довольно изящной идеей, если вы применяете необнуляемость для всех значений, что может быть сделано, если вы добавляете концепцию типа
Option
илиMaybe
для представления типов, которые не всегда могут иметь определенное значение. Это подход, принятый Хаскеллом.Это все хорошо! Но это не исключает использования явно обнуляемых / ненулевых типов для достижения того же эффекта. Почему же тогда Option все еще хорош? В конце концов, Scala поддерживает NULLABLE значения (это имеет к, поэтому он может работать с Java - библиотек) , но опорам ,
Options
а также.В. Так каковы преимущества помимо возможности полного удаления нулей из языка?
А. Состав
Если вы делаете наивный перевод из нулевого кода
к опциональному коду
нет большой разницы! Но это также ужасный способ использовать параметры ... Этот подход намного чище:
Или даже:
Когда вы начинаете работать со списком параметров, он становится еще лучше. Представьте, что список
people
сам по себе необязателен:Как это работает?
Соответствующий код с нулевыми проверками (или даже elvis?: Operator) будет мучительно длинным. Реальный трюк здесь - это операция flatMap, которая позволяет осуществлять вложенное понимание параметров и коллекций так, чтобы никогда не получить значения, допускающие обнуляемость.
источник
flatMap
будет называться(>>=)
оператор связывания для монад. Правильно, Haskellers так любятflatMap
пинговать вещи, что мы помещаем это в логотип нашего языка.Option<T>
никогда не будет нулевым. К сожалению, Scala все еще связан с Java :-) (С другой стороны, если Scala не очень хорошо играл с Java, кто бы это использовал? Oo)Поскольку люди, кажется, скучают по нему:
null
это неоднозначно.Дата-оф-рождения Алисы
null
. Что это означает?Дата-из-смерти Боба
null
. Что это значит?«Разумная» интерпретация может заключаться в том, что дата рождения Алисы существует, но неизвестна, тогда как дата смерти Боба не существует (Боб все еще жив). Но почему мы получили разные ответы?
Еще одна проблема:
null
это крайний случай.null = null
?nan = nan
?inf = inf
?+0 = -0
?+0/0 = -0/0
?Ответы обычно бывают «да», «нет», «да», «да», «нет», «да» соответственно. Сумасшедшие "математики" называют NaN "нулем" и говорят, что он сравнивается равным себе. SQL рассматривает нули как не равные чему-либо (поэтому они ведут себя как NaN). Интересно, что происходит, когда вы пытаетесь сохранить ± ∞, ± 0 и NaN в одном столбце базы данных (есть 2 53 NaN, половина из которых является «отрицательной»).
Что еще хуже, базы данных различаются в том, как они обрабатывают NULL, и большинство из них не согласованы (см. Обработка NULL в SQLite для обзора). Это довольно ужасно.
А теперь для обязательного рассказа:
Недавно я разработал (sqlite3) таблицу базы данных с пятью столбцами
a NOT NULL, b, id_a, id_b NOT NULL, timestamp
. Поскольку это общая схема, предназначенная для решения общей проблемы для довольно произвольных приложений, существуют два ограничения уникальности:id_a
существует только для совместимости с существующим дизайном приложения (частично потому, что я не придумал лучшего решения) и не используется в новом приложении. Из-за того, как NULL работает в SQL, я могу вставить(1, 2, NULL, 3, t)
и(1, 2, NULL, 4, t)
и не нарушать первое ограничение уникальности (потому что(1, 2, NULL) != (1, 2, NULL)
).Это работает именно из-за того, что NULL работает с ограничением уникальности для большинства баз данных (предположительно, так проще моделировать «реальные» ситуации, например, нет двух людей, имеющих одинаковый номер социального страхования, но не у всех есть один).
FWIW, без предварительного вызова неопределенного поведения, ссылки C ++ не могут "указывать на" null, и невозможно создать класс с неинициализированными ссылочными переменными-членами (если выбрасывается исключение, конструирование завершается неудачно).
Примечание. Иногда вам могут понадобиться взаимоисключающие указатели (т. Е. Только один из них может быть ненулевым), например, в гипотетической iOS
type DialogState = NotShown | ShowingActionSheet UIActionSheet | ShowingAlertView UIAlertView | Dismissed
. Вместо этого я вынужден делать что-то вродеassert((bool)actionSheet + (bool)alertView == 1)
.источник
assert(actionSheet ^ alertView)
? Или не может ваш язык XOR Bools?Нежелательность наличия ссылок / указателей может быть обнуляемой по умолчанию.
Я не думаю, что это основная проблема с нулями, главная проблема с нулями в том, что они могут означать две вещи:
Языки, которые поддерживают типы Option, также обычно запрещают или не поощряют использование неинициализированных переменных.
Как работают типы опций, включая стратегии для облегчения проверки нулевых случаев, таких как сопоставление с образцом.
Чтобы быть эффективными, типы Option должны поддерживаться непосредственно на языке. В противном случае для их моделирования требуется много кода котельной плиты. Сопоставление с образцом и вывод типа - это две ключевые функции языка, с которыми легко работать с типами Option. Например:
В F #:
Однако на языке, подобном Java, без прямой поддержки типов Option, у нас будет что-то вроде:
Альтернативное решение, такое как сообщение есть ноль
«Сообщение, едящее ноль» в Objective-C - это не столько решение, сколько попытка облегчить головную боль при проверке нуля. По сути, вместо того, чтобы генерировать исключение времени выполнения при попытке вызвать метод для нулевого объекта, выражение вместо этого оценивает себя как нулевое. Приостановление неверия, как если бы каждый экземпляр метода начинался с
if (this == null) return null;
. Но затем происходит потеря информации: вы не знаете, вернул ли метод значение null, потому что он является допустимым возвращаемым значением, или потому что объект на самом деле является нулевым. Это очень похоже на проглатывание исключений и не дает никакого прогресса в решении проблем с обнуляемым ранее.источник
Ассамблея принесла нам адреса, также известные как нетипизированные указатели. C отображал их напрямую как типизированные указатели, но вводил нулевое значение Алгола как уникальное значение указателя, совместимое со всеми типизированными указателями. Большая проблема с нулем в C состоит в том, что, поскольку каждый указатель может быть нулем, никто не может безопасно использовать указатель без ручной проверки.
В языках более высокого уровня иметь значение null неудобно, поскольку оно действительно выражает два разных понятия:
Наличие неопределенных переменных в значительной степени бесполезно и приводит к неопределенному поведению всякий раз, когда они происходят. Полагаю, все согласятся, что любой ценой следует избегать неопределенности.
Второй случай - это необязательный параметр, который лучше всего указывать явно, например, с типом параметра .
Допустим, мы в транспортной компании, и нам нужно создать приложение, которое поможет составить расписание для наших водителей. Для каждого водителя мы храним несколько сведений, таких как: водительские права, которые у них есть, и номер телефона, по которому можно позвонить в случае чрезвычайной ситуации.
В C мы могли бы иметь:
Как вы заметили, при любой обработке нашего списка драйверов нам придется проверять наличие нулевых указателей. Компилятор вам не поможет, безопасность программы зависит от ваших плеч.
В OCaml тот же код будет выглядеть так:
Давайте теперь скажем, что мы хотим напечатать имена всех водителей вместе с номерами их лицензий на грузовики.
В С:
В OCaml это будет:
Как вы можете видеть в этом тривиальном примере, в безопасной версии нет ничего сложного:
В то время как в C вы могли просто забыть нулевую проверку и бум ...
Примечание: эти примеры кода не скомпилированы, но я надеюсь, что вы получили идеи.
источник
NULL
что «ссылка, которая может ни на что не указывать» была изобретена для некоторого языка Алгол (Википедия согласна, см. En.wikipedia.org/wiki/Null_pointer#Null_pointer ). Но, конечно, вполне вероятно, что программисты на ассемблере инициализировали свои указатели с неверного адреса (читай: Null = 0).У Microsoft Research есть интересный проект, который называется
Это расширение C # с ненулевым типом и некоторым механизмом, чтобы проверить ваши объекты на предмет отсутствия нулей , хотя, IMHO, применение дизайна по принципу контракта может быть более подходящим и более полезным для многих неприятных ситуаций, вызванных нулевыми ссылками.
источник
Исходя из .NET фона, я всегда думал, что у null есть смысл, это полезно. Пока я не узнал о структурах и о том, как легко с ними работать, избегая большого количества стандартного кода. Тони Хоар, выступавший на QCon London в 2009 году, извинился за изобретение пустой ссылки . Процитирую его:
Смотрите этот вопрос тоже у программистов
источник
Роберт Нистром предлагает хорошую статью здесь:
http://journal.stuffwithstuff.com/2010/08/23/void-null-maybe-and-nothing/
описание его мыслительного процесса при добавлении поддержки отсутствия и неудачи в его язык программирования Сорока .
источник
Я всегда смотрел на нуль (или ноль) как на отсутствие значения .
Иногда ты этого хочешь, иногда нет. Это зависит от домена, с которым вы работаете. Если отсутствие значимо: никакого отчества, ваше приложение может действовать соответственно. С другой стороны, если там не должно быть нулевого значения: имя пустое, тогда разработчик получает общеизвестный телефонный звонок в 2 часа ночи.
Я также видел код перегруженный и слишком сложный с проверками на ноль. Для меня это означает одну из двух вещей:
а) ошибка выше в дереве приложений
б) плохой / неполный дизайн
С положительной стороны - Null, вероятно, является одним из наиболее полезных понятий для проверки, если что-то отсутствует, и языки без концепции null в конечном итоге будут усложнять вещи, когда придет время проверять данные. В этом случае, если новая переменная не инициализирована, указанные языки обычно устанавливают переменные в пустую строку, 0 или пустую коллекцию. Однако, если пустая строка или 0 или пустая коллекция являются допустимыми значениями для вашего приложения - тогда у вас есть проблема.
Иногда это можно обойти, изобретая специальные / странные значения для полей, чтобы представить неинициализированное состояние. Но что происходит, когда специальное значение вводится благонамеренным пользователем? И давайте не будем вдаваться в неразбериху, которая будет связана с процедурами проверки данных. Если бы язык поддерживал нулевую концепцию, все проблемы исчезли бы.
источник
Векторные языки могут иногда обходиться без нуля.
В этом случае пустой вектор служит типизированным нулем.
источник