О дебатах о необнуляемых типах - PullRequest
7 голосов
/ 13 марта 2009

Я продолжаю слышать, как люди говорят о том, что ненулевые ссылочные типы могут решить множество ошибок и сделать программирование намного проще. Даже создатель нуля называет это своей миллиардной ошибкой , а Spec # ввел необнуляемые типы для решения этой проблемы.

РЕДАКТИРОВАТЬ: Игнорировать мой комментарий о Spec #. Я неправильно понял, как это работает.

РЕДАКТИРОВАТЬ 2: Я должен говорить с неправильными людьми, я действительно надеялся, что кто-то поспорит с: -)


Так что я бы догадался, будучи в меньшинстве, что я неправ, но я не могу понять, почему эта дискуссия имеет какую-то ценность. Я вижу ноль как инструмент для поиска ошибок. Учтите следующее:

class Class { ... }

void main() {
    Class c = nullptr;
    // ... ... ... code ...
    for(int i = 0; i < c.count; ++i) { ... }
}

БАМ! Нарушение доступа. Кто-то забыл инициализировать c.


Теперь рассмотрим это:

class Class { ... }

void main() {
    Class c = new Class(); // set to new Class() by default
    // ... ... ... code ...
    for(int i = 0; i < c.count; ++i) { ... }
}

Упс. Цикл молча пропускается. Чтобы отследить проблему, может потребоваться некоторое время.


Если ваш класс пуст, код все равно не будет работать. Почему бы системе не сказать вам (хотя и немного грубо) вместо того, чтобы разбираться в этом самостоятельно?

Ответы [ 8 ]

12 голосов
/ 21 сентября 2009

Немного странно, что ответ, помеченный как «ответ» в этой теме, фактически выдвигает на первый план проблему с нулем, а именно:

Я также обнаружил, что большая часть моего NULL ошибки указателя вращаются вокруг функции от забыть проверить возврат функций из string.h, где NULL используется в качестве индикатора.

Не было бы неплохо, если бы компилятор мог перехватывать подобные ошибки во время компиляции, а не во время выполнения?

Если вы использовали ML-подобный язык (SML, OCaml, SML и F # в некоторой степени) или Haskell, ссылочные типы не обнуляются. Вместо этого вы представляете «нулевое» значение, заключая его в тип параметра. Таким образом, вы фактически изменяете тип возвращаемого значения функции, если она может возвращать значение null в качестве допустимого значения. Итак, допустим, я хотел вытащить пользователя из базы данных:

let findUser username =
    let recordset = executeQuery("select * from users where username = @username")
    if recordset.getCount() > 0 then
        let user = initUser(recordset)
        Some(user)
    else
        None

Поиск пользователя имеет тип val findUser : string -> user option, поэтому возвращаемый тип функции фактически говорит вам, что она может возвращать нулевое значение. Чтобы использовать код, вам нужно обработать случаи Some и None:

match findUser "Juliet Thunderwitch" with
| Some x -> print_endline "Juliet exists in database"
| None -> print_endline "Juliet not in database"

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

Теперь мы видим проблему в примере кода OP:

class Class { ... }

void main() {
    Class c = new Class(); // set to new Class() by default
    // ... ... ... code ...
    for(int i = 0; i < c.count; ++i) { ... }
}

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

6 голосов
/ 17 марта 2009

Я не понимаю ваш пример. Если ваш "= new Class ()" является просто заполнителем вместо отсутствия нуля, то это (на мой взгляд) очевидно ошибка. Если это не так, то настоящая ошибка заключается в том, что «...» неправильно установил свое содержимое, что в обоих случаях одинаково.

Исключение, которое показывает, что вы забыли инициализировать c, сообщит вам, в какой момент он не инициализирован, но не там, где он должен был быть инициализирован. Точно так же пропущенный цикл (неявно) скажет вам, где нужно иметь ненулевой .count, но не то, что нужно было сделать или где. Я не вижу ни того, ни другого проще для программиста.

Я не думаю, что смысл "без нуля" заключается в том, чтобы просто выполнить текстовое обнаружение и замену и превратить их в пустые экземпляры. Это явно бесполезно. Смысл в том, чтобы структурировать ваш код так, чтобы ваши переменные никогда не находились в состоянии, когда они указывают на бесполезные / неправильные значения, из которых NULL является просто наиболее распространенным.

2 голосов
/ 13 апреля 2009

Идея ненулевых типов состоит в том, чтобы позволить компилятору, а не вашему клиенту, находить ошибки. Предположим, вы добавили к своему языку два спецификатора типа @nullable (может быть null) и @nonnull (никогда не null) (я использую синтаксис аннотации Java).

Когда вы определяете функцию, вы аннотируете ее аргументы. Например, следующий код скомпилирует

int f(@nullable Foo foo) {
  if (foo == null) 
    return 0;
  return foo.size();
}

Несмотря на то, что foo может иметь значение null на входе, поток управления гарантирует, что при вызове foo.size () foo не равен null.

Но если вы удалите проверку на null, вы получите ошибку во время компиляции.

Следующее также скомпилируется, потому что foo не равен null при вводе:

int g(@nonnull Foo foo) {
  return foo.size(); // OK
}

Однако вы не сможете вызвать g с помощью обнуляемого указателя:

@nullable Foo foo;
g(foo); // compiler error!

Компилятор выполняет анализ потока для каждой функции, поэтому он может определить, когда @nullable становится @nonnull (например, внутри оператора if, который проверяет наличие нуля). Он также примет достоверное определение @nonnull при условии его немедленной инициализации.

@nonnull Foo foo = new Foo();

На эту тему есть гораздо больше в моем блоге .

2 голосов
/ 13 марта 2009

Я признаю, что на самом деле я мало читал о Spec #, но я понял, что NonNullable - это, по сути, атрибут, который вы указываете для параметра, а не обязательно для объявления переменной; Превратите свой пример во что-то вроде:

class Class { ... }

void DoSomething(Class c)
{
    if (c == null) return;
    for(int i = 0; i < c.count; ++i) { ... }
}

void main() {
    Class c = nullptr;
    // ... ... ... code ...
    DoSomething(c);
}

В Spec # вы отмечаете doSomething, чтобы сказать, что «параметр c не может быть нулевым». Мне кажется, что это хорошая возможность, поскольку мне не нужна первая строка в методе DoSomething () (это строка, которую легко забыть, и совершенно бессмысленная для контекста DoSomething ()).

0 голосов
/ 21 января 2011

Необнуляемые типы имеют больше смысла для меня, когда мы имеем дело с объектами домена. Когда вы отображаете таблицы базы данных на объекты и у вас есть необнуляемые столбцы. Допустим, у вас есть таблица с именем User, и в ней есть столбец userid varchar (20), который нельзя обнулять;

Было бы так удобно иметь класс User со строковым полем UserId, который не может иметь значение NULL. Вы можете уменьшить количество ошибок во время компиляции.

0 голосов
/ 20 сентября 2009

Я сейчас работаю над этой темой в C #. .NET имеет Nullable для типов значений, но для ссылочных типов не существует обратной функции.

Я создал NotNullable для ссылочных типов и переместил проблему из if (больше не проверяется на null) в домен типа данных. Это заставляет приложение генерировать исключения во время выполнения, а не во время компиляции.

0 голосов
/ 17 марта 2009

На мой взгляд, есть две области, где используется ноль.

Первое - это отсутствие значения. Например, логическое значение может быть истинным или ложным, или пользователь еще не выбрал параметр, следовательно, ноль. Это полезно и хорошо, но, возможно, изначально оно было реализовано неправильно, и сейчас есть попытка формализовать это использование. (Должен ли быть второй логический элемент для хранения установленного / неустановленного состояния или нулевой как часть логики с тремя состояниями?)

Второй - в смысле нулевого указателя. Чаще всего это программная ошибка, т.е. исключение. Это не заданное состояние, есть ошибка программы. Это должно быть под зонтиком формальных исключений, как это реализовано в современных языках. То есть NullException перехватывается с помощью блока try / catch.

Итак, что из этого вас интересует?

0 голосов
/ 13 марта 2009

Мне нравится использование NULL. Прошло много времени с тех пор, как я работал на C ++, но это очень облегчило поиск моих проблем, касающихся возвращаемых значений и ссылок, и их исправление.

...