Billion-dollar mistake
От: samius Япония http://sams-tricks.blogspot.com
Дата: 11.12.09 17:01
Оценка: 28 (3) :)
Вот собственно, наткнулся и удивился... У больших людей большие ошибки.

http://en.wikipedia.org/wiki/C._A._R._Hoare

Speaking at a conference in 2009, Hoare apologized for inventing the null reference, described by him as a "billion-dollar mistake":

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.


Но нужно быть по-настоящему великим, чтобы признавать такие ошибки.
Re: Billion-dollar mistake
От: IT Россия linq2db.com
Дата: 11.12.09 17:31
Оценка: 1 (1) +8
Здравствуйте, samius, Вы писали:

S>Но нужно быть по-настоящему великим, чтобы признавать такие ошибки.


Думаю, если бы не он, то без проблем кто-то другой.
Если нам не помогут, то мы тоже никого не пощадим.
Re: Billion-dollar mistake
От: ole! США http://files.rsdn.org/4543/rsdn.gif
Дата: 11.12.09 21:00
Оценка:
Здравствуйте, samius, Вы писали:



S>Но нужно быть по-настоящему великим, чтобы признавать такие ошибки.


нужно быть по-настоящему великим, чтобы их не делать изначально!
предложите альтернативу nullptr
my $.02
Re[2]: Billion-dollar mistake
От: samius Япония http://sams-tricks.blogspot.com
Дата: 11.12.09 21:23
Оценка:
Здравствуйте, ole!, Вы писали:

O>Здравствуйте, samius, Вы писали:


S>>Но нужно быть по-настоящему великим, чтобы признавать такие ошибки.


O>нужно быть по-настоящему великим, чтобы их не делать изначально!


Не делает великих ошибок тот, кто ничего не делает великого.

O>предложите альтернативу nullptr


Это не мое предложение, но тем не менее, альтернативы есть:
http://lukeplant.me.uk/blog/posts/null-pointers-vs-none-vs-maybe/
Re: Billion-dollar mistake
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 12.12.09 14:13
Оценка: +2
Здравствуйте, samius, Вы писали:

S>Вот собственно, наткнулся и удивился... У больших людей большие ошибки.

S>http://en.wikipedia.org/wiki/C._A._R._Hoare
S>Speaking at a conference in 2009, Hoare apologized for inventing the null reference, described by him as a "billion-dollar mistake":

Здесь ошибки не было. Любая альтернатива в виде отдельного признака "есть тут данные или нет" в разы хуже и порочнее.

S> I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.


Хоар сделал тогда всё ПРАВИЛЬНО. Или не Хоар, но тоже правильно. Пустые указатели — очень частный случай неприятности вида "данные за пределами допустимого множества", а их использование ничуть не коварнее выхода за пределы массива. Более того, их легче поймать и исключить автоматизированно.

Неправильно было другое: когда появилась возможность сделать гарантированно ненулевые указатели (а появилась она где-то с появлением исключений) — ею не воспользовались.

Пусть это кому-то обидно, но я больше верю тогдашнему Хоару (в частности, изобретателю quick sort), чем нынешнему.
The God is real, unless declared integer.
Re[2]: Billion-dollar mistake
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.12.09 21:55
Оценка:
Здравствуйте, netch80, Вы писали:

N>Здравствуйте, samius, Вы писали:


S>>Вот собственно, наткнулся и удивился... У больших людей большие ошибки.

S>>http://en.wikipedia.org/wiki/C._A._R._Hoare
S>>Speaking at a conference in 2009, Hoare apologized for inventing the null reference, described by him as a "billion-dollar mistake":

N>Здесь ошибки не было. Любая альтернатива в виде отдельного признака "есть тут данные или нет" в разы хуже и порочнее.

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

S>> I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.


N>Хоар сделал тогда всё ПРАВИЛЬНО. Или не Хоар, но тоже правильно. Пустые указатели — очень частный случай неприятности вида "данные за пределами допустимого множества", а их использование ничуть не коварнее выхода за пределы массива. Более того, их легче поймать и исключить автоматизированно.


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

N>Неправильно было другое: когда появилась возможность сделать гарантированно ненулевые указатели (а появилась она где-то с появлением исключений) — ею не воспользовались.


Вот тут я не очень понимаю, причем тут исключения. Для этих целей нужна правильная система типов, которая не позволит сделать (int*)0x00. А исключения помогут лишь делать проверку постфактум, уже после того как гарантированно ненулевой указатель оказался равным null.

N>Пусть это кому-то обидно, но я больше верю тогдашнему Хоару (в частности, изобретателю quick sort), чем нынешнему.


Я склонен полагать что это один и тот же человек.
Re[3]: Billion-dollar mistake
От: Мишень-сан  
Дата: 12.12.09 23:19
Оценка: 2 (1) +7
Здравствуйте, samius

В этой ветке и в других перемешаны, как мне кажется, понятия указателя и ссылки.
Так вот, для указателя null или 0 — полностью валидное значение.
А вот для ссылки наличие специального значения null является причиной множества задниц, головной боли и писанины.
В этом отношении мне очень нравится нынешний С++ — там указатель и ссылка являются различными понятиями.
Причём тамошняя ссылка всегда ссылается на реально существующий объект и может быть инициализирована только один раз.
Второе не так существенно, а вот первое — очень и очень.

В .NET и Java произошло некое подобие слияния ссылок и указателей
(да, в .NET, по крайней мере в C#, есть чистые указатели, но не о них дальше речь).
Выражается это в том, что тамошние ссылки могут принимать то самое специальное значение — null.
Вот именно это мне кажется неправильным подходом.
Гораздо логичней было бы сделать ссылки non-nullable, как и обычные value types.
И распространить функционал Nullable<> на все типы.
Таким образом nullable было бы не поведением по умолчанию, а явно задаваемым, прямо в интерфейсе.
А сейчас на каждый чих приходится проверять, не подсунули ли нам null вместо объекта.
Re[4]: Billion-dollar mistake
От: samius Япония http://sams-tricks.blogspot.com
Дата: 12.12.09 23:47
Оценка:
Здравствуйте, Мишень-сан, Вы писали:

МС>Здравствуйте, samius


МС>В этой ветке и в других перемешаны, как мне кажется, понятия указателя и ссылки.

МС>Так вот, для указателя null или 0 — полностью валидное значение.
МС>А вот для ссылки наличие специального значения null является причиной множества задниц, головной боли и писанины.
МС>В этом отношении мне очень нравится нынешний С++ — там указатель и ссылка являются различными понятиями.
МС>Причём тамошняя ссылка всегда ссылается на реально существующий объект и может быть инициализирована только один раз.
МС>Второе не так существенно, а вот первое — очень и очень.

МС>В .NET и Java произошло некое подобие слияния ссылок и указателей

МС>(да, в .NET, по крайней мере в C#, есть чистые указатели, но не о них дальше речь).
МС>Выражается это в том, что тамошние ссылки могут принимать то самое специальное значение — null.
МС>Вот именно это мне кажется неправильным подходом.

В C# аналогом C++ ссылки является не ссылочный тип, конструкция для передачи параметров по ссылке (ref keyword). Именно она может быть проинициализирована лишь один раз и не null-ом. А ссылочные типы это близкие аналоги C++ указателей, но без нужды разыменования их и без возможности взятия адреса.

МС>Гораздо логичней было бы сделать ссылки non-nullable, как и обычные value types.


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

МС>И распространить функционал Nullable<> на все типы.


Здравая идея (в совокупности с предыдущей)

МС>Таким образом nullable было бы не поведением по умолчанию, а явно задаваемым, прямо в интерфейсе.

МС>А сейчас на каждый чих приходится проверять, не подсунули ли нам null вместо объекта.

Конечно, гораздо проще было бы указывать в контрактах что значение опционально явно, вместо проверок на null.
Re[3]: Billion-dollar mistake
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.12.09 09:05
Оценка:
Здравствуйте, samius, Вы писали:

N>>Здесь ошибки не было. Любая альтернатива в виде отдельного признака "есть тут данные или нет" в разы хуже и порочнее.

S>Как-то слишком категорично звучит. Цель не в том, чтобы ввести отдельные признкаки, а в том, чтобы не дать разыменовать нечто, где может быть null, на уровне языка. В частности признаком может служить сам null.

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

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

int doUseful(MainData* notnull md, AuxData1* ad1, AuxData2* ad2)
{
...
}

и защита состоит в том, что
1) разыменование указателя без notnull запрещено языком;
2) попытка конверсии простого указателя к notnull указателю вызывает исключение, если указатель таки пустой (null).

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

Разумеется, это не единственный способ, хотя для императивных языков — близко к тому. Смысл именно в том, чтобы запрет сделать notnull указатель пустым было невозможно обойти.

Возвращаясь к теме особого значения — это настолько частый приём, что пронизывает всё программирование. В Unix API половина функций возвращает -1 при неудаче (под что даже система типов заточена — см. ssize_t против size_t). В Win32 API аналог — INVALID_HANDLE_VALUE, а в таких местах как User & GDI — значения из MAKESTRING как указатели в файле ресурсов. И тэ дэ и тэ пэ. Если ты считаешь, что пустой указатель некорректен — почему ты не выступаешь против этих применений?

N>>Хоар сделал тогда всё ПРАВИЛЬНО. Или не Хоар, но тоже правильно. Пустые указатели — очень частный случай неприятности вида "данные за пределами допустимого множества", а их использование ничуть не коварнее выхода за пределы массива. Более того, их легче поймать и исключить автоматизированно.

S>Проверка выхода за пределы массива существует давно, а вот системы, позволяющие описывать контракты методов и делать статический анализ кода пока не сильно распространены. Но даже если эту неприятность легче исключить, чем выход за пределы массива, то что именно правильного в null?

Не "правильно", а "минимально неправильно из всех вариантов" (так что выбор как действие — правильный, а выбор как результат — необязательно). Остальное я сказал выше.

N>>Неправильно было другое: когда появилась возможность сделать гарантированно ненулевые указатели (а появилась она где-то с появлением исключений) — ею не воспользовались.


S>Вот тут я не очень понимаю, причем тут исключения. Для этих целей нужна правильная система типов, которая не позволит сделать (int*)0x00.


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

S> А исключения помогут лишь делать проверку постфактум, уже после того как гарантированно ненулевой указатель оказался равным null.


Нет. См. выше.

N>>Пусть это кому-то обидно, но я больше верю тогдашнему Хоару (в частности, изобретателю quick sort), чем нынешнему.

S>Я склонен полагать что это один и тот же человек.

В одну реку нельзя зайти дважды, потому что в ней текут другие воды. Думаю, тебе есть что считать неправильным в своих прошлых поступках (неважно, в 20, 10 или 2 года), независимо от того, были они правильны или нет. За 44 года человек меняется неузнаваемо.
The God is real, unless declared integer.
Re[4]: Billion-dollar mistake
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.12.09 09:17
Оценка: +2 -1
Здравствуйте, Мишень-сан, Вы писали:

МС>В этой ветке и в других перемешаны, как мне кажется, понятия указателя и ссылки.

МС>Так вот, для указателя null или 0 — полностью валидное значение.
МС>А вот для ссылки наличие специального значения null является причиной множества задниц, головной боли и писанины.
МС>В этом отношении мне очень нравится нынешний С++ — там указатель и ссылка являются различными понятиями.
МС>Причём тамошняя ссылка всегда ссылается на реально существующий объект и может быть инициализирована только один раз.

Это не "перемешано", это Вы судите именно исходя из своего неверного представления о том разделении, как сделано в C++.

Более того, в C++ нет реальной защиты против пустой ссылки. Например, там можно сделать

void do1(foo* p)
{
  p& ref = *p;
  do2(p);
}


и проверки не будет. Я проверил компиляцию этого кода — на максимуме предупреждений оно не сказало ни слова про то, что якобы что-то не то. Проверял gcc 4.2.1, 4.3.5, 4.4.2.

Ссылка C++ отличается от указателя тем, что она константна и выглядит как аргумент сам по себе, без указателя — и не более того. Ваше утверждение про "ссылается на реально существующий объект" не соответствует действительности, и нарушение может быть совершенно невольным, как в приведённом примере.

МС>В .NET и Java произошло некое подобие слияния ссылок и указателей

МС>(да, в .NET, по крайней мере в C#, есть чистые указатели, но не о них дальше речь).
МС>Выражается это в том, что тамошние ссылки могут принимать то самое специальное значение — null.
МС>Вот именно это мне кажется неправильным подходом.
МС>Гораздо логичней было бы сделать ссылки non-nullable, как и обычные value types.
МС>И распространить функционал Nullable<> на все типы.

Без поддержки компилятора это убьёт возможность статической проверки типов, а с такой поддержкой совершенно неважно, какой вариант считать выделенным — nullable или notnull.
The God is real, unless declared integer.
Re[5]: Billion-dollar mistake
От: Мишень-сан  
Дата: 13.12.09 12:04
Оценка:
Здравствуйте, netch80, Вы писали:

N>Более того, в C++ нет реальной защиты против пустой ссылки. Например, там можно сделать


N>
N>void do1(foo* p)
N>{
N>  p& ref = *p;
N>  do2(p);
N>}
N>


N>и проверки не будет. Я проверил компиляцию этого кода — на максимуме предупреждений оно не сказало ни слова про то, что якобы что-то не то. Проверял gcc 4.2.1, 4.3.5, 4.4.2.


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

N>Ссылка C++ отличается от указателя тем, что она константна и выглядит как аргумент сам по себе, без указателя — и не более того. Ваше утверждение про "ссылается на реально существующий объект" не соответствует действительности, и нарушение может быть совершенно невольным, как в приведённом примере.


Однако изначально ссылки предполагалось использовать для реально существующих объектов. А в примере выше любой нормальный человек вставит хотя бы проверку на null. Потому что для указателей 0 — норма, а для ссылок — нет.

N>Без поддержки компилятора это убьёт возможность статической проверки типов, а с такой поддержкой совершенно неважно, какой вариант считать выделенным — nullable или notnull.


.NET среда и так поддерживает Nullable<>, давая этому дженерику особый статус. Попробуйте сделать для Nullable<int> GetType(). И получите почему-то int, а не Nullable<int>.
Ну а nullable или notnull — тут не могу однозначно аргументировать, что лучше. Nullable мне кажется логичней, т.к. мы не сужаем множество допустимых значений, а расширяем его.
Re[4]: Billion-dollar mistake
От: WolfHound  
Дата: 13.12.09 12:15
Оценка: +5
Здравствуйте, netch80, Вы писали:

N>Нее, ты не понял. Представь себе функцию, у которой на входе, например, одна структура, которая всегда есть, ещё две, которых может не быть, если не нужно (тогда передаётся пустой указатель) и в структурах тоже могут быть указатели на данные, которых ещё нет / уже нет / и не должно быть (нужное подчеркнуть). Ты будешь для каждого такого значения давать отдельный флаг "есть это поле"? Или всё-таки придумаешь специальное значение самому указателю? Я говорю именно про это.

Ни то и ни другое.
Посмотри как это сделано в функциональщине.
Там используют алгебраический тип для указания опциональных данных.
variant Option[T]
{
| Some { value : T }
| None
}


N>int doUseful(MainData* notnull md, AuxData1* ad1, AuxData2* ad2)

N>{
N> ...
N>}
Этот код превращается в
int doUseful(MainData* md, Option[AuxData1*] ad1, Option[AuxData2*] ad2)
{
  ...
}


N>и защита состоит в том, что

N>1) разыменование указателя без notnull запрещено языком;
N>2) попытка конверсии простого указателя к notnull указателю вызывает исключение, если указатель таки пустой (null).
В данном случае защита заключается в том что Option это другой тип.
И подобные привидения просто не пропустит типизатор.
Для того чтобы Option[T] превратить в T нам понадобится сделать это явно при помощи сравнения с образцом.

Данная система типов имеет еще одно преимущество. Можно сделать Option[Option[T]].
Зачем это надо?
Ну например имея вот такую реализацию словоря:
class Dictionary[Key, Valu]
{
...
    Option[Value] Get(key)
    {
    ...
    }
...
}

Мы сможем сделать так Dictionary[string, Option[string]] в результате Get будет возвращать Option[Option[string]]
Соответственно мы можем легко различить ситуацию когда в словарь положили None и когда в словарь не положили ничего.
Как это сделать в твоей системе типов?

N>Возвращаясь к теме особого значения — это настолько частый приём, что пронизывает всё программирование. В Unix API половина функций возвращает -1 при неудаче (под что даже система типов заточена — см. ssize_t против size_t). В Win32 API аналог — INVALID_HANDLE_VALUE, а в таких местах как User & GDI — значения из MAKESTRING как указатели в файле ресурсов. И тэ дэ и тэ пэ. Если ты считаешь, что пустой указатель некорректен — почему ты не выступаешь против этих применений?

Лично я выступаю против.
API современных ОС просто ужасно.
Хотя учитывая на чем они написаны это не удивительно.

N>См. выше. Не нужна такая система типов, потому что эта конверсия может быть допустимой.

При нормальной системе типов подобные конверсии не нужны. См выше...

N>А нужна такая, которая не позволит разыменовать пустой указатель.

Нужна такая которая не позволяет их иметь.
... << RSDN@Home 1.2.0 alpha 4 rev. 1305>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[4]: Billion-dollar mistake
От: Mazay Россия  
Дата: 13.12.09 14:43
Оценка:
Здравствуйте, Мишень-сан, Вы писали:

МС>Причём тамошняя ссылка всегда ссылается на реально существующий объект и может быть инициализирована только один раз.

Это если не хранить ссылки на объекты дольше самих объектов .
Главное гармония ...
Re[4]: Billion-dollar mistake
От: Mazay Россия  
Дата: 13.12.09 15:06
Оценка:
Здравствуйте, netch80, Вы писали:

N>Здравствуйте, samius, Вы писали:


N>>>Здесь ошибки не было. Любая альтернатива в виде отдельного признака "есть тут данные или нет" в разы хуже и порочнее.

S>>Как-то слишком категорично звучит. Цель не в том, чтобы ввести отдельные признкаки, а в том, чтобы не дать разыменовать нечто, где может быть null, на уровне языка. В частности признаком может служить сам null.

N>Нее, ты не понял. Представь себе функцию, у которой на входе, например, одна структура, которая всегда есть, ещё две, которых может не быть, если не нужно (тогда передаётся пустой указатель) и в структурах тоже могут быть указатели на данные, которых ещё нет / уже нет / и не должно быть (нужное подчеркнуть). Ты будешь для каждого такого значения давать отдельный флаг "есть это поле"? Или всё-таки придумаешь специальное значение самому указателю? Я говорю именно про это.


N>А как с ним дальше работать — вот это уже интересный вопрос. Например, если первая структура всегда должна быть — требуем от языка поддержки модификатора notnull, тогда получается что-то вроде


N>int doUseful(MainData* notnull md, AuxData1* ad1, AuxData2* ad2)

N>{
N> ...
N>}

N>и защита состоит в том, что

N>1) разыменование указателя без notnull запрещено языком;
N>2) попытка конверсии простого указателя к notnull указателю вызывает исключение, если указатель таки пустой (null).
Угу. И исключения InvalidCastException посыпятся как из приложения на Java 1.4 (пока не было дженериков).

По-хорошему твой код должен выглядеть слдующим образом:

int doUseful(MainData* notnull md, AuxData1* notnull ad1, AuxData2* notnull ad2)
{

}

int doUseful(MainData* notnull md, AuxData1* notnull ad1)
{
  ...
}

int doUseful(MainData* notnull md, AuxData2* notnull ad2)
{
  ...
}

int doUseful(MainData* notnull md)
{
  ...
}


Какие варианты возможны — зависит уже от логики. Это всяко лучше чем ту-же логику делать на if-ах в одной версии doUseful(). Как видно из кода — указателей без notnull нет вообще. Все контролируется системой типов во время компиляции. Уменьшить объем кода опять же можно стандартными средствами типа шаблонов/дженериков.
Главное гармония ...
Re[4]: Billion-dollar mistake
От: samius Япония http://sams-tricks.blogspot.com
Дата: 13.12.09 19:39
Оценка: +1
Здравствуйте, netch80, Вы писали:

N>Нее, ты не понял. Представь себе функцию, у которой на входе, например, одна структура, которая всегда есть, ещё две, которых может не быть, если не нужно (тогда передаётся пустой указатель) и в структурах тоже могут быть указатели на данные, которых ещё нет / уже нет / и не должно быть (нужное подчеркнуть). Ты будешь для каждого такого значения давать отдельный флаг "есть это поле"? Или всё-таки придумаешь специальное значение самому указателю? Я говорю именно про это.


Специальное значение — полумеры. Я за специальный тип.
WolfHound описал, как это сделано в функциональных языках, но алгебраический тип — не единственный путь.

N>и защита состоит в том, что

N>1) разыменование указателя без notnull запрещено языком;
N>2) попытка конверсии простого указателя к notnull указателю вызывает исключение, если указатель таки пустой (null).

N>Далее обвешиваем подобной защитой все части кода и функции, где не может быть пустой указатель... цель достигнута. В результате, в данном примере исключение будет не при разыменовании, а при попытке входа в функцию с пустым значением md.


N>Разумеется, это не единственный способ, хотя для императивных языков — близко к тому. Смысл именно в том, чтобы запрет сделать notnull указатель пустым было невозможно обойти.


Уже такой подход будет немногим лучше чем тип указателя со спецзначением. Немногим — это потому что исключение при обращении конверсии пустого указателя к notnull не сильно отличается от NullReferenceException. Такой подход лишь сдержит распространение null значений по программе но не снимет проблему окончательно.

Алгебраический тип + PM дают более полное решение, т.к. исключают саму возможность исключительной ситуации.

N>Возвращаясь к теме особого значения — это настолько частый приём, что пронизывает всё программирование. В Unix API половина функций возвращает -1 при неудаче (под что даже система типов заточена — см. ssize_t против size_t). В Win32 API аналог — INVALID_HANDLE_VALUE, а в таких местах как User & GDI — значения из MAKESTRING как указатели в файле ресурсов. И тэ дэ и тэ пэ. Если ты считаешь, что пустой указатель некорректен — почему ты не выступаешь против этих применений?


Э.. я против, но выступать не собираюсь. Я не считаю, что нужно сломать и переписать заново весь код, который использует любые проявления спецзначений от null до -1 и INVALID_HANDLE_VALUE.
Я считаю что в высокоуровневых языках и фреймворках использование null должно быть сведено к минимуму а то и вообще прекращено.
Так пока работаешь с F# и его родными библиотеками, не боишься что null где-то пролезет, а потому любые проверки на null становятся не нужны. Как только приходится взаимодействовать с FCL или другими библиотеками, написанными на других .NET языках с актвивным использованием null, начинается геморрой с null-ом и на F#.

S>>Но даже если эту неприятность легче исключить, чем выход за пределы массива, то что именно правильного в null?


N>Не "правильно", а "минимально неправильно из всех вариантов" (так что выбор как действие — правильный, а выбор как результат — необязательно). Остальное я сказал выше.

Это значит что правильно сделал что ввел, но итог получился не очень хорош? Если я правильно тебя понял, то частично я могу согласиться. Если бы не Хоар, null ввел бы кто-нибудь другой и с вероятностью 0,999 его форма не отличалась бы от той, что мы имеем благодаря Хоар-у.

S>>Вот тут я не очень понимаю, причем тут исключения. Для этих целей нужна правильная система типов, которая не позволит сделать (int*)0x00.


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


Здесь я опять согласен с WolfHound-ом.

N>>>Пусть это кому-то обидно, но я больше верю тогдашнему Хоару (в частности, изобретателю quick sort), чем нынешнему.

S>>Я склонен полагать что это один и тот же человек.

N>В одну реку нельзя зайти дважды, потому что в ней текут другие воды. Думаю, тебе есть что считать неправильным в своих прошлых поступках (неважно, в 20, 10 или 2 года), независимо от того, были они правильны или нет. За 44 года человек меняется неузнаваемо.


Соглашусь, что человек меняется. Но эти изменения не имеют случайный характер. Взгляды на одни и те же вещи могут меняться в том числе и благодаря накопленному опыту, а не просто потому что человек стал другим.
Re[5]: Billion-dollar mistake
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.12.09 22:35
Оценка: -1
Здравствуйте, WolfHound, Вы писали:

N>>Нее, ты не понял. Представь себе функцию, у которой на входе, например, одна структура, которая всегда есть, ещё две, которых может не быть, если не нужно (тогда передаётся пустой указатель) и в структурах тоже могут быть указатели на данные, которых ещё нет / уже нет / и не должно быть (нужное подчеркнуть). Ты будешь для каждого такого значения давать отдельный флаг "есть это поле"? Или всё-таки придумаешь специальное значение самому указателю? Я говорю именно про это.

WH>Ни то и ни другое.
WH>Посмотри как это сделано в функциональщине.

По твоему описанию как раз и получилась вариация на тему nullable. При этом детали её внутреннего устройства сокрыты (в реальности может передаваться и отдельный флаг, но где он — ведает компилятор, а не программист).

WH>Данная система типов имеет еще одно преимущество. Можно сделать Option[Option[T]].

[...]
WH>Соответственно мы можем легко различить ситуацию когда в словарь положили None и когда в словарь не положили ничего.
WH>Как это сделать в твоей системе типов?

А которая система "моя"? Та, в которой notnull? Наверно, никак, но я и не вижу полезности в таком стиле различения, он уже слишком заковырист.

N>>Возвращаясь к теме особого значения — это настолько частый приём, что пронизывает всё программирование. В Unix API половина функций возвращает -1 при неудаче (под что даже система типов заточена — см. ssize_t против size_t). В Win32 API аналог — INVALID_HANDLE_VALUE, а в таких местах как User & GDI — значения из MAKESTRING как указатели в файле ресурсов. И тэ дэ и тэ пэ. Если ты считаешь, что пустой указатель некорректен — почему ты не выступаешь против этих применений?

WH>Лично я выступаю против.
WH>API современных ОС просто ужасно.
WH>Хотя учитывая на чем они написаны это не удивительно.

API действительно почти везде ужасно, но у меня лично ужас вызывают совсем не эти моменты — с ними как раз достаточно легко справиться. А вот попробуй справься, например, с тем, что O_NONBLOCK ставится не на операцию или дескриптор, а на ofile в целом. (В Windows, однако, это ещё кошмарнее сделано.)

N>>См. выше. Не нужна такая система типов, потому что эта конверсия может быть допустимой.

WH>При нормальной системе типов подобные конверсии не нужны. См выше...
N>>А нужна такая, которая не позволит разыменовать пустой указатель.
WH>Нужна такая которая не позволяет их иметь.

Это нереальное и непродуктивное требование. Маскированные варианты через тот же Option не меняют общий подход.
The God is real, unless declared integer.
Re[5]: Billion-dollar mistake
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.12.09 22:44
Оценка:
Здравствуйте, Mazay, Вы писали:

M>Угу. И исключения InvalidCastException посыпятся как из приложения на Java 1.4 (пока не было дженериков).


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

M>По-хорошему твой код должен выглядеть слдующим образом:


M>int doUseful(MainData* notnull md, AuxData1* notnull ad1, AuxData2* notnull ad2)

M>int doUseful(MainData* notnull md, AuxData1* notnull ad1)
M>int doUseful(MainData* notnull md, AuxData2* notnull ad2)
M>int doUseful(MainData* notnull md)

Это — по-хорошему?? Вот за такое как раз — расстрел через повешение на медленном огне (заменяется на написание драйверов на Excel).
The God is real, unless declared integer.
Re[6]: Billion-dollar mistake
От: Mazay Россия  
Дата: 13.12.09 22:56
Оценка:
Здравствуйте, netch80, Вы писали:

N>Здравствуйте, Mazay, Вы писали:


M>>Угу. И исключения InvalidCastException посыпятся как из приложения на Java 1.4 (пока не было дженериков).


N>Вот и пусть сыпятся, что лучше, чем реальный доступ по кривому указателю.

Чем лучше-то? Тем что вместо NullReferenceException вылетит InvalidCastException? Один фиг проверка в рантайме.

M>>По-хорошему твой код должен выглядеть слдующим образом:


M>>int doUseful(MainData* notnull md, AuxData1* notnull ad1, AuxData2* notnull ad2)

M>>int doUseful(MainData* notnull md, AuxData1* notnull ad1)
M>>int doUseful(MainData* notnull md, AuxData2* notnull ad2)
M>>int doUseful(MainData* notnull md)

N>Это — по-хорошему?? Вот за такое как раз — расстрел через повешение на медленном огне (заменяется на написание драйверов на Excel).


А что тебя собственно пугает? То что много функций? Ну тогда напиши код для твоей единой версии doUseful(). Будут те же яйца только на if-ах.
Главное гармония ...
Re[5]: Billion-dollar mistake
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.12.09 23:03
Оценка:
Здравствуйте, samius, Вы писали:

N>>Нее, ты не понял. Представь себе функцию, у которой на входе, например, одна структура, которая всегда есть, ещё две, которых может не быть, если не нужно (тогда передаётся пустой указатель) и в структурах тоже могут быть указатели на данные, которых ещё нет / уже нет / и не должно быть (нужное подчеркнуть). Ты будешь для каждого такого значения давать отдельный флаг "есть это поле"? Или всё-таки придумаешь специальное значение самому указателю? Я говорю именно про это.

S>Специальное значение — полумеры. Я за специальный тип.

Пусть специальный тип. Но в его реализации должно быть не только это. Например, конструирование произвольного указателя из целого числа или из буфера должно быть под соответствующей защитой. Выход за пределы массива или отведённой области — тоже. (Что приводит нас, например, к слайсам Go.)

S>WolfHound описал, как это сделано в функциональных языках, но алгебраический тип — не единственный путь.


Угу.

N>>Разумеется, это не единственный способ, хотя для императивных языков — близко к тому. Смысл именно в том, чтобы запрет сделать notnull указатель пустым было невозможно обойти.

S>Уже такой подход будет немногим лучше чем тип указателя со спецзначением. Немногим — это потому что исключение при обращении конверсии пустого указателя к notnull не сильно отличается от NullReferenceException. Такой подход лишь сдержит распространение null значений по программе но не снимет проблему окончательно.

Ну почему же. Если часть кода целиком переведена на использование такого notnull (а также использование честно полученных указателей, см. выше) — то в ней уже можно не опасаться таких проблем. А дальше постепенной передвижкой границы мы сузим опасные места до предела...

N>>Возвращаясь к теме особого значения — это настолько частый приём, что пронизывает всё программирование. В Unix API половина функций возвращает -1 при неудаче (под что даже система типов заточена — см. ssize_t против size_t). В Win32 API аналог — INVALID_HANDLE_VALUE, а в таких местах как User & GDI — значения из MAKESTRING как указатели в файле ресурсов. И тэ дэ и тэ пэ. Если ты считаешь, что пустой указатель некорректен — почему ты не выступаешь против этих применений?

S>Э.. я против, но выступать не собираюсь. Я не считаю, что нужно сломать и переписать заново весь код, который использует любые проявления спецзначений от null до -1 и INVALID_HANDLE_VALUE.
S>Я считаю что в высокоуровневых языках и фреймворках использование null должно быть сведено к минимуму а то и вообще прекращено.

Ну так у тебя будут в итоге те же яйца, но вид в профиль. Например, мне в Erlang'е несколько раз приходилось делать операцию типа "выберем значение из проплиста во втором элементе в пятом поле, а если его нет — из проплиста в третьем элементе третьего поля, а если и его нет — из седьмого элемента". Выглядит кошмарно, но деться некуда. Не будет у тебя null — заменят просто на факт присутствия элемента в какой-то структуре данных.

N>>Не "правильно", а "минимально неправильно из всех вариантов" (так что выбор как действие — правильный, а выбор как результат — необязательно). Остальное я сказал выше.

S>Это значит что правильно сделал что ввел, но итог получился не очень хорош? Если я правильно тебя понял, то частично я могу согласиться. Если бы не Хоар, null ввел бы кто-нибудь другой и с вероятностью 0,999 его форма не отличалась бы от той, что мы имеем благодаря Хоар-у.

Именно так (по последнему предложению). И действительно, форма бы наверняка совпала — потому что она сама напрашивалась.

S>>>Вот тут я не очень понимаю, причем тут исключения. Для этих целей нужна правильная система типов, которая не позволит сделать (int*)0x00.

N>>См. выше. Не нужна такая система типов, потому что эта конверсия может быть допустимой. А нужна такая, которая не позволит разыменовать пустой указатель.
S>Здесь я опять согласен с WolfHound-ом.

WolfHound даёт практически такой же результат, как у меня — разница в форме. От того, что у него указатель до поры до времени "спрятан" в Option, подход к возможному существованию указателя совпадает. Может быть, преимущество в том, что он таки может быть нулевым и не пустым при том:)

N>>>>Пусть это кому-то обидно, но я больше верю тогдашнему Хоару (в частности, изобретателю quick sort), чем нынешнему.

S>>>Я склонен полагать что это один и тот же человек.
N>>В одну реку нельзя зайти дважды, потому что в ней текут другие воды. Думаю, тебе есть что считать неправильным в своих прошлых поступках (неважно, в 20, 10 или 2 года), независимо от того, были они правильны или нет. За 44 года человек меняется неузнаваемо.
S>Соглашусь, что человек меняется. Но эти изменения не имеют случайный характер. Взгляды на одни и те же вещи могут меняться в том числе и благодаря накопленному опыту, а не просто потому что человек стал другим.

В данном случае не могу согласиться. Если бы речь шла об опыте и о его грамотном осознании, была бы предложена альтернатива. Как легко видеть, альтернативы не предложено, а реально она появилась в пределах доступа широких народных масс не раньше пары последних лет (почему мы собственно эту дискуссию и ведём). Это никак не 65-й год.
The God is real, unless declared integer.
Re[6]: Billion-dollar mistake
От: samius Япония http://sams-tricks.blogspot.com
Дата: 14.12.09 00:12
Оценка:
Здравствуйте, netch80, Вы писали:

N>Здравствуйте, samius, Вы писали:


S>>Специальное значение — полумеры. Я за специальный тип.


N>Пусть специальный тип. Но в его реализации должно быть не только это. Например, конструирование произвольного указателя из целого числа или из буфера должно быть под соответствующей защитой. Выход за пределы массива или отведённой области — тоже. (Что приводит нас, например, к слайсам Go.)


Вот возможность конструирования произвольного указателя и сломает все старания по уходу от null-ов.

S>>Уже такой подход будет немногим лучше чем тип указателя со спецзначением. Немногим — это потому что исключение при обращении конверсии пустого указателя к notnull не сильно отличается от NullReferenceException. Такой подход лишь сдержит распространение null значений по программе но не снимет проблему окончательно.


N>Ну почему же. Если часть кода целиком переведена на использование такого notnull (а также использование честно полученных указателей, см. выше) — то в ней уже можно не опасаться таких проблем. А дальше постепенной передвижкой границы мы сузим опасные места до предела...


Мы только лишь одни исключения заменим другими.

S>>Э.. я против, но выступать не собираюсь. Я не считаю, что нужно сломать и переписать заново весь код, который использует любые проявления спецзначений от null до -1 и INVALID_HANDLE_VALUE.

S>>Я считаю что в высокоуровневых языках и фреймворках использование null должно быть сведено к минимуму а то и вообще прекращено.

N>Ну так у тебя будут в итоге те же яйца, но вид в профиль. Например, мне в Erlang'е несколько раз приходилось делать операцию типа "выберем значение из проплиста во втором элементе в пятом поле, а если его нет — из проплиста в третьем элементе третьего поля, а если и его нет — из седьмого элемента". Выглядит кошмарно, но деться некуда. Не будет у тебя null — заменят просто на факт присутствия элемента в какой-то структуре данных.


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

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

S>>Здесь я опять согласен с WolfHound-ом.

N>WolfHound даёт практически такой же результат, как у меня — разница в форме. От того, что у него указатель до поры до времени "спрятан" в Option, подход к возможному существованию указателя совпадает. Может быть, преимущество в том, что он таки может быть нулевым и не пустым при том


Преимущество в том, что в Option не добраться до значения (указателя), если его нет.

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


N>В данном случае не могу согласиться. Если бы речь шла об опыте и о его грамотном осознании, была бы предложена альтернатива. Как легко видеть, альтернативы не предложено, а реально она появилась в пределах доступа широких народных масс не раньше пары последних лет (почему мы собственно эту дискуссию и ведём). Это никак не 65-й год.


Я не был на той конференции. Быть может формат конференции не позволил Хоару указать альтернативу, а может просто в википедии об этом не написали.
Да и нужна ли была альтернатива тогда? По высказыванию Хоара

But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement.

можно косвенно судить о том, что необходимости в null-е не было. Да и тот факт что в ALGOL-е не было null-а до 65-го года, тоже кое-что значит.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.