Лучшее объяснение для языков без нуля

225

Время от времени, когда программисты жалуются на нулевые ошибки / исключения, кто-то спрашивает, что мы делаем без нуля.

У меня есть некоторое базовое представление о крутизне типов опций, но я не обладаю знаниями или языковыми навыками, чтобы лучше всего это выразить. Что является отличным объяснением следующего, написанного так, чтобы это было доступно обычному программисту, на которого мы могли бы указать этому человеку?

  • Нежелательно иметь ссылки / указатели быть обнуляемыми по умолчанию
  • Как работают типы опций, включая стратегии для облегчения проверки нулевых случаев, таких как
    • сопоставление с образцом и
    • монадические понимания
  • Альтернативное решение, такое как сообщение есть ноль
  • (другие аспекты, которые я пропустил)
Роман А. Тайчер
источник
11
Если вы добавите теги к этому вопросу для функционального программирования или F #, вы обязательно получите фантастические ответы.
Стивен Свенсен
Я добавил функциональный программный тег, так как тип опции пришел из мира ml. Я бы предпочел не отмечать это F # (слишком конкретно). Кстати, кто-то с полномочиями таксономии должен добавить теги типа «возможно» или «вариант».
Роман А. Тайчер
4
я подозреваю, что такие специальные теги не нужны. Теги в основном предназначены для того, чтобы люди могли находить соответствующие вопросы (например, «вопросы, о которых я много знаю и на которые я смогу ответить», и «функциональное программирование» очень полезно там. Но что-то вроде «ноль» или « option-type "гораздо менее полезен. Мало кто, вероятно, будет следить за тегом" option-type ", ища вопросы, на которые они могут ответить.;)
jalf 21.10.10
Давайте не будем забывать, что одна из главных причин нулевой ситуации заключается в том, что компьютеры эволюционировали, сильно привязанные к теории множеств. Нуль является одним из наиболее важных множеств во всей теории множеств. Без этого целые алгоритмы сломались бы. Например, выполнить сортировку слиянием. Это включает в себя разбить список пополам несколько раз. Что делать, если список состоит из 7 пунктов? Сначала вы делите его на 4 и 3. Затем 2, 2, 2 и 1. Затем 1, 1, 1, 1, 1, 1, 1 и .... null! У Null есть цель, которую вы практически не видите. Это существует больше для теоретического царства.
stevendesu
6
@steven_desu - я не согласен. В языках с нулевым значением вы можете иметь ссылку на пустой список [], а также ссылку на пустой список. Этот вопрос относится к путанице между ними.
Стусмит

Ответы:

434

Я думаю, что краткое резюме того, почему ноль нежелателен, состоит в том, что бессмысленные состояния не должны быть представимыми .

Предположим, я моделирую дверь. Он может находиться в одном из трех состояний: открыто, закрыто, но разблокировано, закрыто и заблокировано. Теперь я мог бы смоделировать его в соответствии с

class Door
    private bool isShut
    private bool isLocked

и ясно, как отобразить мои три состояния в эти две логические переменные. Но это оставляет четвертое, нежелательное состояние доступно: isShut==false && isLocked==true. Поскольку типы, которые я выбрал в качестве своего представления, допускают это состояние, я должен приложить умственные усилия, чтобы гарантировать, что класс никогда не попадет в это состояние (возможно, путем явного кодирования инварианта). Напротив, если бы я использовал язык с алгебраическими типами данных или проверенные перечисления, которые позволяют мне определить

type DoorState =
    | Open | ShutAndUnlocked | ShutAndLocked

тогда я мог бы определить

class Door
    private DoorState state

и больше нет забот. Система типов гарантирует, что существует только три возможных состояния для экземпляра class Door. Это то, к чему хорошо подходят системы типов - явное исключение целого класса ошибок во время компиляции.

Проблема в nullтом, что каждый ссылочный тип получает это дополнительное состояние в своем пространстве, которое обычно нежелательно. stringПеременная может быть любая последовательность символов, или это может быть этот сумасшедший дополнительное nullзначение , которое не отображает в моей проблемной области. У Triangleобъекта есть три Points, у которых есть Xи Yзначения, и , к сожалению, сами Pointпо Triangleсебе или s могут быть этим сумасшедшим нулевым значением, которое не имеет смысла для графической области, в которой я работаю. И т.д.

Если вы намереваетесь смоделировать потенциально несуществующее значение, вы должны явно указать его. Если я собираюсь смоделировать людей так, чтобы у каждого Personбыли a FirstNameи a LastName, но только у некоторых людей есть MiddleNames, то я хотел бы сказать что-то вроде

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

где stringздесь предполагается ненулевой тип. Тогда нет никаких хитрых инвариантов для установления и никаких неожиданных NullReferenceExceptions при попытке вычислить длину чьего-либо имени. Система типов гарантирует, что любой код, имеющий дело с MiddleNameучетными записями, может быть таким None, а любой код, имеющий дело с этим, FirstNameможет с уверенностью предположить, что там есть значение.

Например, используя приведенный выше тип, мы могли бы написать эту глупую функцию:

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

без забот. Напротив, в языке с обнуляемыми ссылками для таких типов, как строка, тогда предполагается

class Person
    private string FirstName
    private string MiddleName
    private string LastName

в конечном итоге вы создаете такие вещи, как

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

который взрывается, если входящий объект Person не имеет инварианта всего, что является ненулевым, или

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)

или, может быть

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

при условии, что это pгарантирует наличие первого / последнего, но середина может быть нулевой, или, может быть, вы делаете проверки, которые выдают различные типы исключений, или кто знает что. Все эти сумасшедшие варианты реализации и вещи, о которых нужно подумать, возникают, потому что есть эта глупая представимая ценность, которая вам не нужна или не нужна.

Нуль обычно добавляет ненужную сложность. Сложность - враг всего программного обеспечения, и вы должны стремиться уменьшать сложность всякий раз, когда это целесообразно.

(Обратите внимание, что даже эти простые примеры сложнее. Даже если a FirstNameне может быть null, a stringможет представлять ""(пустую строку), что, вероятно, также не является личным именем, которое мы намереваемся смоделировать. Таким образом, даже с не обнуляемые строки, все равно может случиться так, что мы «представляем бессмысленные значения». Опять же, вы можете решить бороться с этим либо с помощью инвариантов и условного кода во время выполнения, либо с помощью системы типов (например, чтобы иметь NonEmptyStringтип). последний, вероятно, опрометчив («хорошие» типы часто «закрываются» по набору общих операций и, например NonEmptyString, не закрываются по.SubString(0,0)), но это демонстрирует больше точек в пространстве дизайна. В конце концов, в любой системе типов есть некоторая сложность, от которой будет очень хорошо избавиться, и другая сложность, от которой просто сложнее избавиться. Ключевым моментом в этом разделе является то, что почти во всех системах типов изменение с «необнуляемых ссылок по умолчанию» на «необнуляемые ссылки по умолчанию» почти всегда является простым изменением, которое делает систему типов намного лучше в борьбе со сложностью и исключение определенных типов ошибок и бессмысленных состояний. Поэтому довольно безумно, что многие языки повторяют эту ошибку снова и снова.)

Брайан
источник
31
Re: имена - действительно. И, может быть, вы заботитесь о том, чтобы смоделировать дверь, которая открыта, но с торчащим засовом, препятствующим закрытию двери. В мире много сложностей. Ключ не состоит в том, чтобы добавить больше сложности при реализации отображения между «состояниями мира» и «состояниями программы» в вашем программном обеспечении.
Брайан
59
Что, ты никогда не закрывал двери открытыми?
Джошуа
58
Я не понимаю, почему люди возбуждаются из-за семантики конкретного домена. Брайан кратко и просто представил недостатки с нулем, да, он упростил проблемную область в своем примере, сказав, что у каждого есть имя и фамилия. На вопрос был дан ответ «Т», Брайан - если ты когда-нибудь будешь в Бостоне, я должен тебе пива за все твои публикации здесь!
акафеном
67
@akaphenom: спасибо, но учтите, что не все люди пьют пиво (я не пью). Но я ценю, что вы просто используете упрощенную модель мира, чтобы выразить благодарность, поэтому я не буду больше прислушиваться к ошибочным предположениям вашей модели мира. : P (Столько сложности в реальном мире! :))
Брайан
4
Как ни странно, в этом мире есть 3 государства! Они используются в некоторых отелях как туалетные двери. Кнопка действует как ключ изнутри, который запирает дверь снаружи. Он автоматически разблокируется, как только задвижка сдвинется.
комонад
65

Хорошая вещь о типах опций не в том, что они необязательны. Это то, что все другие типы не являются .

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

Но часто нам это не нужно, и разрешение такого «нулевого» состояния приводит только к двусмысленности и путанице: каждый раз, когда я обращаюсь к переменной ссылочного типа в .NET, я должен учитывать, что она может быть нулевой .

Часто он никогда не будет нулевым, потому что программист структурирует код так, что это никогда не произойдет. Но компилятор не может это проверить, и каждый раз, когда вы его видите, вы должны спросить себя: «Может ли это быть нулевым? Нужно ли здесь проверять нулевое?»

В идеале, во многих случаях, когда ноль не имеет смысла, его нельзя допускать .

Это сложно достичь в .NET, где почти все может быть нулевым. Вы должны положиться на автора кода, который вы называете, чтобы он был на 100% дисциплинированным и последовательным и четко задокументировал, что может и не может быть нулевым, или вы должны быть параноиком и проверять все .

Однако, если типы по умолчанию не обнуляются , вам не нужно проверять, являются ли они пустыми. Вы знаете, что они никогда не могут быть нулевыми, потому что компилятор / средство проверки типов навязывает это вам.

И тогда нам нужны просто задняя дверь для тех редких случаев , когда мы делаем необходимость обрабатывать состояние нуля. Затем можно использовать тип «option». Затем мы допускаем нулевое значение в тех случаях, когда мы сознательно приняли решение о том, что нам необходимо представлять случай «без значения», а во всех остальных случаях мы знаем, что значение никогда не будет нулевым.

Как уже упоминалось, в C # или Java, например, нуль может означать одно из двух:

  1. переменная неинициализирована. Это в идеале никогда не должно происходить. Переменная не должна существовать, если она не инициализирована.
  2. переменная содержит некоторые «необязательные» данные: она должна быть в состоянии представить случай, когда нет данных . Это иногда необходимо. Возможно, вы пытаетесь найти объект в списке, и вы не знаете заранее, есть ли он там. Тогда нам нужно представить, что «объект не найден».

Второе значение должно быть сохранено, но первое должно быть полностью исключено. И даже второе значение не должно быть значением по умолчанию. Это то, что мы можем выбрать, если и когда нам это нужно . Но когда нам не нужно что-то необязательное, мы хотим, чтобы средство проверки типов гарантировало, что оно никогда не будет нулевым.

jalf
источник
И во втором смысле, мы хотим, чтобы компилятор предупредил (остановил?) Нас, если мы попытаемся получить доступ к таким переменным, не проверив сначала на нулевое значение. Вот отличная статья о грядущей нулевой / ненулевой функции C # (наконец-то!) Blogs.msdn.microsoft.com/dotnet/2017/11/15/…
Охад Шнайдер
44

Все ответы до сих пор фокусируются на том, почему nullэто плохо, и как это удобно, если язык может гарантировать, что определенные значения никогда не будут нулевыми.

Затем они продолжают предполагать, что было бы довольно изящной идеей, если вы применяете необнуляемость для всех значений, что может быть сделано, если вы добавляете концепцию типа Optionили Maybeдля представления типов, которые не всегда могут иметь определенное значение. Это подход, принятый Хаскеллом.

Это все хорошо! Но это не исключает использования явно обнуляемых / ненулевых типов для достижения того же эффекта. Почему же тогда Option все еще хорош? В конце концов, Scala поддерживает NULLABLE значения (это имеет к, поэтому он может работать с Java - библиотек) , но опорам , Optionsа также.

В. Так каковы преимущества помимо возможности полного удаления нулей из языка?

А. Состав

Если вы делаете наивный перевод из нулевого кода

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

к опциональному коду

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

нет большой разницы! Но это также ужасный способ использовать параметры ... Этот подход намного чище:

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

Или даже:

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

Когда вы начинаете работать со списком параметров, он становится еще лучше. Представьте, что список peopleсам по себе необязателен:

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

Как это работает?

//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)

Соответствующий код с нулевыми проверками (или даже elvis?: Operator) будет мучительно длинным. Реальный трюк здесь - это операция flatMap, которая позволяет осуществлять вложенное понимание параметров и коллекций так, чтобы никогда не получить значения, допускающие обнуляемость.

Кевин Райт
источник
8
+1, это хороший момент, чтобы подчеркнуть. Одно дополнение: в Хаскел-ланде flatMapбудет называться (>>=)оператор связывания для монад. Правильно, Haskellers так любят flatMapпинговать вещи, что мы помещаем это в логотип нашего языка.
CA McCann
1
+1 Надеюсь, выражение Option<T>никогда не будет нулевым. К сожалению, Scala все еще связан с Java :-) (С другой стороны, если Scala не очень хорошо играл с Java, кто бы это использовал? Oo)
Достаточно просто сделать: 'List (null) .headOption'. Обратите внимание, что это означает совсем другое, чем возвращаемое значение None
Кевин Райт,
4
Я дал вам награду, так как мне действительно нравится то, что вы сказали о композиции, о которой другие люди, кажется, не упоминали.
Роман А. Тайчер
Отличный ответ с отличными примерами!
thSoft
38

Поскольку люди, кажется, скучают по нему: 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. Поскольку это общая схема, предназначенная для решения общей проблемы для довольно произвольных приложений, существуют два ограничения уникальности:

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

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).

дц.
источник
Фактические математики не используют понятие "NaN", хотя, будьте уверены.
Нолдорин
@Noldorin: они делают, но они используют термин «неопределенная форма».
IJ Кеннеди
@IJKennedy: Это другой колледж, который я знаю очень хорошо, спасибо. Некоторые «NaN» могут представлять неопределенную форму, но, поскольку FPA не выполняет символическое рассуждение, приравнивание его к неопределенной форме вводит в заблуждение!
Нолдорин
Что не так с assert(actionSheet ^ alertView)? Или не может ваш язык XOR Bools?
кот
16

Нежелательность наличия ссылок / указателей может быть обнуляемой по умолчанию.

Я не думаю, что это основная проблема с нулями, главная проблема с нулями в том, что они могут означать две вещи:

  1. Ссылка / указатель неинициализированы: проблема здесь та же, что и с изменчивостью в целом. С одной стороны, это затрудняет анализ вашего кода.
  2. Нулевая переменная на самом деле означает что-то: это тот случай, когда типы Option фактически формализуются.

Языки, которые поддерживают типы Option, также обычно запрещают или не поощряют использование неинициализированных переменных.

Как работают типы опций, включая стратегии для облегчения проверки нулевых случаев, таких как сопоставление с образцом.

Чтобы быть эффективными, типы Option должны поддерживаться непосредственно на языке. В противном случае для их моделирования требуется много кода котельной плиты. Сопоставление с образцом и вывод типа - это две ключевые функции языка, с которыми легко работать с типами Option. Например:

В 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;")

Однако на языке, подобном Java, без прямой поддержки типов Option, у нас будет что-то вроде:

//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());

Альтернативное решение, такое как сообщение есть ноль

«Сообщение, едящее ноль» в Objective-C - это не столько решение, сколько попытка облегчить головную боль при проверке нуля. По сути, вместо того, чтобы генерировать исключение времени выполнения при попытке вызвать метод для нулевого объекта, выражение вместо этого оценивает себя как нулевое. Приостановление неверия, как если бы каждый экземпляр метода начинался с if (this == null) return null;. Но затем происходит потеря информации: вы не знаете, вернул ли метод значение null, потому что он является допустимым возвращаемым значением, или потому что объект на самом деле является нулевым. Это очень похоже на проглатывание исключений и не дает никакого прогресса в решении проблем с обнуляемым ранее.

Стивен Свенсен
источник
Это любимая мозоль, но c # вряд ли похож на c.
Роман А. Тайчер
4
Я собирался здесь на Java, поскольку у C #, вероятно, было бы более приятное решение ... но я ценю ваше мнение, что на самом деле имеют в виду "язык с синтаксисом, вдохновленным c". Я пошел дальше и заменил «с-как» заявление.
Стивен Свенсен
С linq, верно. Я думал о C # и не заметил этого.
Роман А. Тайчер
1
Да, в основном с синтаксисом, вдохновленным c, но я думаю, что я также слышал о императивных языках программирования, таких как python / ruby, с очень небольшим количеством синтаксиса c-like, называемого c-like функциональными программистами.
Роман А. Тайчер
11

Ассамблея принесла нам адреса, также известные как нетипизированные указатели. C отображал их напрямую как типизированные указатели, но вводил нулевое значение Алгола как уникальное значение указателя, совместимое со всеми типизированными указателями. Большая проблема с нулем в C состоит в том, что, поскольку каждый указатель может быть нулем, никто не может безопасно использовать указатель без ручной проверки.

В языках более высокого уровня иметь значение null неудобно, поскольку оно действительно выражает два разных понятия:

  • Сказать, что что-то не определено .
  • Говорить, что что-то необязательно .

Наличие неопределенных переменных в значительной степени бесполезно и приводит к неопределенному поведению всякий раз, когда они происходят. Полагаю, все согласятся, что любой ценой следует избегать неопределенности.

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


Допустим, мы в транспортной компании, и нам нужно создать приложение, которое поможет составить расписание для наших водителей. Для каждого водителя мы храним несколько сведений, таких как: водительские права, которые у них есть, и номер телефона, по которому можно позвонить в случае чрезвычайной ситуации.

В C мы могли бы иметь:

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;
};

Как вы заметили, при любой обработке нашего списка драйверов нам придется проверять наличие нулевых указателей. Компилятор вам не поможет, безопасность программы зависит от ваших плеч.

В OCaml тот же код будет выглядеть так:

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;
}

Давайте теперь скажем, что мы хотим напечатать имена всех водителей вместе с номерами их лицензий на грузовики.

В С:

#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 ? */
  }
}

В OCaml это будет:

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

Как вы можете видеть в этом тривиальном примере, в безопасной версии нет ничего сложного:

  • Это гораздо сложнее.
  • Вы получаете намного лучшие гарантии, и никакая нулевая проверка не требуется вообще.
  • Компилятор обеспечил, чтобы вы правильно справились с опцией

В то время как в C вы могли просто забыть нулевую проверку и бум ...

Примечание: эти примеры кода не скомпилированы, но я надеюсь, что вы получили идеи.

bltxd
источник
Я никогда не пробовал, но en.wikipedia.org/wiki/Cyclone_%28programming_language%29 утверждает, что разрешает ненулевые указатели для c.
Роман А. Тайчер
1
Я не согласен с вашим утверждением, что никто не заинтересован в первом случае. Многие люди, особенно представители функциональных языковых сообществ, крайне заинтересованы в этом и либо препятствуют, либо полностью запрещают использование неинициализированных переменных.
Стивен Свенсен
Я полагаю, NULLчто «ссылка, которая может ни на что не указывать» была изобретена для некоторого языка Алгол (Википедия согласна, см. En.wikipedia.org/wiki/Null_pointer#Null_pointer ). Но, конечно, вполне вероятно, что программисты на ассемблере инициализировали свои указатели с неверного адреса (читай: Null = 0).
1
@ Стефан: Мы, вероятно, имели в виду то же самое. Для меня они препятствуют или запрещают использование неинициализированных вещей именно потому, что нет смысла обсуждать неопределенные вещи, поскольку мы не можем делать с ними ничего вменяемого или полезного. Это не имело бы никакого интереса.
bltxd
2
как @tc. говорит, что null не имеет ничего общего со сборкой. При сборке типы, как правило, не обнуляются. Значение, загружаемое в регистр общего назначения, может быть равно нулю или может быть некоторым ненулевым целым числом. Но это никогда не может быть нулевым. Даже если вы загружаете адрес памяти в регистр, на большинстве распространенных архитектур отдельное представление «нулевого указателя» отсутствует. Это понятие введено в языки более высокого уровня, такие как C.
jalf 21.10.10
5

У Microsoft Research есть интересный проект, который называется

Spec #

Это расширение C # с ненулевым типом и некоторым механизмом, чтобы проверить ваши объекты на предмет отсутствия нулей , хотя, IMHO, применение дизайна по принципу контракта может быть более подходящим и более полезным для многих неприятных ситуаций, вызванных нулевыми ссылками.

Джахан
источник
4

Исходя из .NET фона, я всегда думал, что у null есть смысл, это полезно. Пока я не узнал о структурах и о том, как легко с ними работать, избегая большого количества стандартного кода. Тони Хоар, выступавший на QCon London в 2009 году, извинился за изобретение пустой ссылки . Процитирую его:

Я называю это моей ошибкой в ​​миллиард долларов. Это было изобретение нулевой ссылки в 1965 году. В то время я разрабатывал первую всеобъемлющую систему типов для ссылок на объектно-ориентированном языке (ALGOL W). Моя цель состояла в том, чтобы гарантировать, что любое использование ссылок должно быть абсолютно безопасным, с проверкой, выполняемой автоматически компилятором. Но я не мог удержаться от соблазна вставить пустую ссылку просто потому, что это было так легко реализовать. Это привело к бесчисленным ошибкам, уязвимостям и сбоям системы, которые, вероятно, причинили миллиард долларов боли и ущерба за последние сорок лет. В последние годы ряд программных анализаторов, таких как PREfix и PREfast в Microsoft, использовались для проверки ссылок и предупреждения, если существует риск, что они могут быть ненулевыми. Более поздние языки программирования, такие как Spec #, ввели объявления для ненулевых ссылок. Это решение, которое я отверг в 1965 году.

Смотрите этот вопрос тоже у программистов

Навфал
источник
1

Я всегда смотрел на нуль (или ноль) как на отсутствие значения .

Иногда ты этого хочешь, иногда нет. Это зависит от домена, с которым вы работаете. Если отсутствие значимо: никакого отчества, ваше приложение может действовать соответственно. С другой стороны, если там не должно быть нулевого значения: имя пустое, тогда разработчик получает общеизвестный телефонный звонок в 2 часа ночи.

Я также видел код перегруженный и слишком сложный с проверками на ноль. Для меня это означает одну из двух вещей:
а) ошибка выше в дереве приложений
б) плохой / неполный дизайн

С положительной стороны - Null, вероятно, является одним из наиболее полезных понятий для проверки, если что-то отсутствует, и языки без концепции null в конечном итоге будут усложнять вещи, когда придет время проверять данные. В этом случае, если новая переменная не инициализирована, указанные языки обычно устанавливают переменные в пустую строку, 0 или пустую коллекцию. Однако, если пустая строка или 0 или пустая коллекция являются допустимыми значениями для вашего приложения - тогда у вас есть проблема.

Иногда это можно обойти, изобретая специальные / странные значения для полей, чтобы представить неинициализированное состояние. Но что происходит, когда специальное значение вводится благонамеренным пользователем? И давайте не будем вдаваться в неразбериху, которая будет связана с процедурами проверки данных. Если бы язык поддерживал нулевую концепцию, все проблемы исчезли бы.

Джон
источник
Привет @Jon, это немного сложно следовать за тобой здесь. Я наконец понял, что под «особыми / странными» значениями вы, вероятно, подразумеваете что-то вроде «неопределенного» в Javascript или «NaN» в IEEE. Но, кроме того, вы не отвечаете ни на один из вопросов, заданных ОП. И утверждение, что «Null, вероятно, является наиболее полезным понятием для проверки, если что-то отсутствует», почти наверняка неверно. Типы параметров - это хорошо продуманная, безопасная для типов альтернатива null.
Стивен Свенсен
@Stephen - На самом деле, оглядываясь назад на мое сообщение, я думаю, что вся вторая половина должна быть перенесена на вопрос, который еще предстоит задать. Но я все еще говорю, что null очень полезен для проверки, если что-то отсутствует.
Джон
0

Векторные языки могут иногда обходиться без нуля.

В этом случае пустой вектор служит типизированным нулем.

Джошуа
источник
Я думаю, что понимаю, о чем вы говорите, но не могли бы вы привести примеры? Особенно применения нескольких функций к возможно нулевому значению?
Роман А. Тайчер
Если применить векторное преобразование к пустому вектору, получится еще один пустой вектор. К вашему сведению, SQL в основном векторный язык.
Иисус Навин
1
Хорошо, я лучше уточнить это. SQL - это векторный язык для строк и язык значений для столбцов.
Иисус Навин