Нашли или выдавили из себя код, который нельзя назвать нормальным,
на который без улыбки не взглянешь?
Не торопитесь его удалять или рефакторить, — запостите его на
говнокод.ру, посмеёмся вместе!
Какой C# )))
Я кста взглянув на код, подумал что это C#.
Вот что в нашем C# хорошо - так это то, что нам не нада писать implements, extends, throws.
Хотя я когда выполнял лабораторки в унике по Java, в throws писал throws Throwable() - а че, мне ваша ява нафиг не сдалась, но нада тупа сдать.
Да, разница между "extends" и "implements" это совершенно ненужная хуйня, которую придумали зачем-то в жабе, ну и конечно мыхомакаки ее скопировали, потому что макаки. В котлине и C# этой чуши нет, в C++ тем более (бо pure abstract а не interface).
"throws" Это checked exceptions: идея была такая: ты или возвращаешь значение, или ошибку. throws заставляет клиента провериться на ошибку. Это решение тут же все возненавидели, и потому в C# (along with Kotlin) этого уже нет.
правильнее было бы сделать монаду и паттерн матчинг, но увы
В го, но если в джаве метод A бросает IOException может быть завернут без дополнительных бубнов в B, у которого в сигнатуре тоже выброс IOException, то в го тебе блядь везде надо ставить ифы, чтобы проверять, не вернул ли дочерний метод ошибку.
Самое смешное, что делать внятный хендлинг ошибок в каждом методе конечно никто не будет. Поэтому гоферы без какой-либо застенчивости возвращают ошибку на уровень выше, типа там выше есть конечный юзер, пусть сам на своем сервере ебаном разбирается чойта мы забыли файл перед обновлением залочить.
Я не умею в го, но слышал, что у него неплохая стандартная либлиотека, и что он всё собирает статически, в результате чего вся программа это один файл, который можно скопировать, и он будет 20 лет работать, пока userland abi не поменяют.
Так же слышал, что там нет обобщенного программирования. Это правда? Как же они делают контейнеры объектов?
> в результате чего вся программа это один файл, который можно скопировать, и он будет 20 лет работать, пока userland abi не поменяют.
да, но и еще многие языки, и даже шарп тебе без проблем соберет единый бинарник (джава сосатб), но об этом еженощно не заливают одни макаки в уши другим
> Так же слышал, что там нет обобщенного программирования. Это правда? Как же они делают контейнеры объектов?
Переписывая один и тот же код под разные типы. И это норма.
Помню, игрунок еще не нашел ничего лучше, как написать об этом не куда-то там, а в medium. А все потому, что икосистеме Visual BRASIC принято не замечать рантайм.
Впрочем, мокрософт заботливо доставит этот рантайм через automatic updates и даже перезагрузит пека для тебя.
> all required .NET Core files to run your app but it doesn't include the native dependencies of .NET Core. These dependencies must be present on the system before the app runs.
> го без libssl или libicu сможет работать
Такой-то перевод стрелок. Как будто .NOT сможет работать со строкой "Hello world!" без кучи предустановленного барахла, но которое к счастью поставляется из коробки вместе с windows analytics.
Ну в джаву optional к счастью еще в восьмерке завезли, а шарп пока подтупливает. В нугете всё есть, конечно, но с говнецой: там Настоящие Функциональщики не делают конструктора OfNullable, потому что конечный метод должен знать, возвращает он пустоту или значение. В теории это конечно правильно, но как мне блядь стыковать это с легаси кодом и библиотеками, которые духом не слыхивали про этот тип из нестандартной библиотеки? Сначала просто мучаешься с x == null ? None : Some(x), потом плюешь и запихиваешь это в свой статический конструктор, потому открываешь следующий проект и там сука опять делаешь то же самое.
when (val userOrError = getUser()) {
is Success -> print("Her name is ${userOrError.name}") //тут userOrError это user
is Failure -> System.err.println("Failed to get user because of ${userOrError.errorCode}") //тут error
}
А «Optional» — замечательный образец анскильного говна. Им крайне удобно сделать быстро и анскильно, а потом долго и нудно разгребать получившееся говно в попытках избавиться от «Упс, что-то пошло не так».
Это проблемы не языка, это проблемы концепта. В большинстве случаев «Optional» — ущербное говно, удобное здесь и сейчас, но создающее серьёзные проблемы в дальней перспективе.
Разумеется, в некоторых случаях «Optional» может быть полезным: например, какому-нибудь методу поиска элемента в коллекции действительно удобнее возвращать «Optional». Но попытки использовать его в более сложных случаях (что-нибудь вроде «db.getUser(username, password) -> Optional<User>») приводят именно к тому, о чём я и сказал: к невразумительным ошибкам и большим сложностям в рефакторинге.
Я считаю что Optional» — ущербное говно, малоудобное даже здесь и сейчас.
Питушарская, тупая обёртка для анскилябр.
>например, какому-нибудь методу поиска элемента в коллекции действительно удобнее возвращать «Optional»
indexOf до сих пор возвращает -1. И никто не жалуется. И не городит вместо int сложных типов c позицией и флагом ошибки.
Тем более что абстракция «Optional» пока что не zero-cost.
> Только лямбды и обёртки снижают скорость кода.
Лямбды — возможно. Обёртки в крестовом зерокост стиле — настолько несущественно и в настолько экзотических случаях, что этим можно пренебречь, поскольку выбор небезопасных интов только из-за скорости в 99.95% случаях будет классической и вредной premature optimization.
> А пользы особой не дают.
Они дают гарантию того, что полученный -1 не будет где-нибудь ниже по коду засунут в индекс. В анскильной питушне с bounds-checking это будет неожиданное исключение, а вот в крестосях — UB, что гораздо печальнее.
>Обёртки в крестовом зерокост стиле — настолько несущественно и в настолько экзотических случаях, что этим можно пренебречь, поскольку выбор небезопасных интов только из-за скорости в 99.95% случаях будет классической и вредной premature optimization.
Ну я же за Царcкий режим топлю.
В общем я считаю Optional частным видом списка из 0 или 1 элемента. А сам список можно представить в виде массива.
>Они дают гарантию того, что полученный -1 не будет где-нибудь ниже по коду засунут в индекс.
Ну не проверять код возврата indexOf, это уж совсем для анскильных лохов.
> В общем я считаю Optional частным видом списка из 0 или 1 элемента.
Он и есть.
> Ну не проверять код возврата indexOf, это уж совсем для анскильных лохов.
Любые программы по определению пишутся невнимательными анскильными лохами (степень анскильности, конечно, варьируется). Одна из главных задач языка программирования высокого уровня — снизить количество ошибок, вызванных анскильностью и невнимательностью. В этом смысле Optional с жёсткими компайл-тайм проверками гораздо лучше простого инта.
Я не спорю. Но пока не видно приличного zero-cost, ложащегося в регистр
struct Index{
uint pos:31
,int error:1
}
>Они дают гарантию того, что полученный -1 не будет где-нибудь ниже по коду засунут в индекс.
Это редкая ошибка.
А вот гораздо более частая ошибка, когда к полученному в indexOf значению (предварительно прочеканному на -1) приплюсовывают что-то эдакое и снова лезут с ним в массив/строку.
И её хер обойдёшь возвращая Optionalы.
>Одна из главных задач языка программирования высокого уровня — снизить количество ошибок, вызванных анскильностью и невнимательностью.
И поэтому вообще нужны типы, которые я описывал в предыдущих дискуссиях. А именно: целые с диапазонами.
indexOf по строке должен возвращать
строго тип int[0,length-searchString.length-1].
А substrы должны принимать строго тип int[0,length-1]
И компилятор при инкременте int[0,length-1] должен возвращать тип int[1,length]. А при добавлении 42 тип будет int[42,length-41]
Соответственно при желании полезть в строку/массив с таким типом программа просто не соберётся.
Тогда в игру вступает проблема останова, и в результате в половине случаев компилятор всё выведет сам, а в другой половине (например, индекс возвращается из другой функции, или из глубины вызовов, или из прошедшего проверку пользовательского ввода, etc) программисту придётся ебаться с явными проверками. При этом правила, когда компилятор может автоматически проверить индекс, а когда не может, будут настолько сложны и запутаны, что программисту придётся компилировать программу только чтобы понять, работает она или нет — или просто проверять индексы везде, что перфоманса не добавит.
> Да. См. ниже. Паттерн-матчинг.
Для самых простых вещей, вроде collection.find(), это оверинжиниринг.
> а в другой половине (например, индекс возвращается из другой функции,
Значит нужно допиливать автовывод типов.
>или из глубины вызовов
С индексной арифметикой, да ещё разбросанной по глубине вызовов можно страшно налажать. Уж лучше иметь ошибку компиляции, чем хрупкий код-пиздец.
В одном месте поменяли, в другом забыли. Another buffer overflow CVE.
>или из прошедшего проверку пользовательского ввода
Проверку он пройдёт только конверсией в нужный тип.
В принципе по объёму кода это одно и то же.
> При этом правила, когда компилятор может автоматически проверить индекс, а когда не может, будут настолько сложны и запутаны
Ничего сложного там нет. Просто buffer overflow говно больше никогда не скомпилится и не попадёт в продакшн.
>просто проверять индексы везде, что перфоманса не добавит.
В случае анскильного отброса с руками из жопы — безусловно.
Положительным моментом будет опять-таки то что buffer overflow говно больше никогда не скомпилится и не попадёт в продакшн.
> Значит нужно допиливать автовывод типов.
Полностью это сделать невозможно.
> buffer overflow
Про эти страшные слова забыли в любых языках с проверками границ.
> Проверку он пройдёт только конверсией в нужный тип.
Как именно? Как нужно будет переписать, например, такой код:
string = getUserInput();
print(string[3]);
?
> Ничего сложного там нет.
Крестокомпилятор до сих пор и компайл-тайм деление на ноль отловить не всегда может, а ты замахиваешься на настолько грандиозные вещи.
> В случае анскильного отброса с руками из жопы — безусловно.
Неанскильный неотброс с руками из плеч тоже ничего не сможет сделать в случае, когда компилятор не сможет правильно доказать корректность индекса.
> buffer overflow говно больше никогда не скомпилится и не попадёт в продакшн
Для этого достаточно просто избавиться от индексов, как от тяжёлого наследия ассемблера.
> Во-первых этот код — опасное говно. И хорошо, что он не скомпилится.
Хорошо, давай так:
def capitalize(string):
string[0] = to_upper(string[0])
...
name = getUserInput()
capitalize(name)
Это достаточно реальный пример. Как его переписать так, чтобы
>>> buffer overflow говно больше никогда не скомпилится и не попадёт в продакшн
?
> И расплатились 2-4 кратным снижением пирфоманса.
Даже в теории, даже для самого плохого случая вида «for i in 0..len(arr): arr[i] += 1» никаких x2 и в помине не будет.
> Там в сишке включали какой-то режим bounds checking и емнип пирфоманс садился в 2-4 раза.
Очевидно, это был простой наброс на хреновый bounds checking. С нормальным чекером и нормальным компилятором, например, такого точно не будет.
Реальный пример: https://gcc.godbolt.org/z/DdP-Qj. «Gcc» достаточно умён, чтобы доказать нинужность лишних проверок границ. В данных случаях получился идеальный зерокост.
> typedef NotEmpty
Не нужно, это лишнее усложнение примеров. Я хочу понять, как программист должен будет говорить компилятору, что данный индекс проверен и точно не вылезет за границы рантайм-массива/строки.
> //здесь оно отвалится
Нужно сделать так, чтобы не отвалилось.
>Нужно сделать так, чтобы не отвалилось.
Это никак невозможно, если придёт пустая строка.
> string[0] = to_upper(string[0])
Либо отвалится здесь, с out of bounds.
>Я хочу понять, как программист должен будет говорить компилятору, что данный индекс проверен и точно не вылезет за границы рантайм-массива/строки.
Явно сконвертировать этот индекс в тип [0..N].
Если конверсия будет неудачной — произойдёт runtime ошибка.
Но т.к. программист уверен в годности индекса, в теории ошибок быть не должно.
> Это никак невозможно, если придёт пустая строка.
Я написал код с ошибкой (выхода за границы массива), которую предложил исправить.
>>> Я хочу понять, как программист должен будет говорить компилятору, что данный индекс проверен и точно не вылезет за границы рантайм-массива/строки.
Просто переведи на «систему типов с диапазонами»:
name = getUserInput()
if len(name) > 0:
name[0] = to_upper(name[0])
> int[0..len(name)-1]
Стоп, стоп, стоп. Это статический или динамический тип?
Ну и в любом случае, для правильного перевода не хватает ручной проверки на пустоту строки, поскольку приведённый мной в последнем комментарии отрывок на ней не упадёт.
>Стоп, стоп, стоп. Это статический или динамический тип?
String[0..N] name = ...
int[0..len(name)-1] //компилятор видит что ф-ция len (string [0..N] )возвращает N+1
int[0..N+1-1] //он делает простую подстановку и получает тип
int[0..N] indx=cast(0); //если мы примем гипотезу о тупости компилера, программист может писать N руками
Такое можно накрестячить даже на современных шаблонах.
>Ну и в любом случае, для правильного перевода не хватает ручной проверки на пустоту строки,
Ага. Ниже пример.
Тем не менее, я всё ещё не вижу никаких реальных преимуществ такой системы даже по сравнению с обычным неявным bounds checking. Абсолютное большинство индексаций в реальном коде происходит на рантайм-массивах либо же с рантайм-индексами. Доступ по компайл-тайм-вычислимому индексу к компайл-тайм-вычислимому массиву, который такая система может проверить в компайл-тайме, крайне редок.
Всё остальное переходит в рантайм-проверки («cast()»).
Более того, как я показывал выше (https://gcc.godbolt.org/z/DdP-Qj), мощные компиляторы вполне способны проводить индексные доказательства и выкидывать лишние проверки даже без диапазонных (хотя, конечно, в данном случае правильнее будет «предикатных») типов.
>Всё остальное переходит в рантайм-проверки («cast()»).
Нет. cast нужен только для абсолютных чисел (42) по рандомным строкам полученным извне. Поскольку юзер всегда может ввести строку длиной меньше 42.
В cast не переходят
Srting x="kokoko";
x[3];
В cast не переходят штуки с indexOf/substring, завязанные на строку.
>Абсолютное большинство индексаций в реальном коде происходит на рантайм-массивах либо же с рантайм-индексами.
Поправочка. На рантайм-массивах либо жевместе с рантайм-индексами.
А тип рантайм-индекса, у нас всегда завязан на рантайм-размер рантайм-массива.
Обычная задача парсинга: найти начало подстроки, найти конец подстроки. Вырезать нужное.
indexOfы будут возвращать тип [O..N] которым можно будет индексировать строку без всяких кастов.
> мощные компиляторы вполне способны проводить индексные доказательства и выкидывать лишние проверки
Даже ява с 7ой версии научилась.
Но для примера сложнее тестового, компилер без подсказок в виде предлагаемой диапазонной типизации может обосраться с их выпиливанием.
И совершенно неважно, известно ли содержимое pages на этапе компиляции. Здесь в любом случае потребуются рантайм-проверки, либо через неявную проверку границ, либо через явный if.
> Обычная задача парсинга: найти начало подстроки, найти конец подстроки. Вырезать нужное.
> indexOfы будут возвращать тип [O..N] которым можно будет индексировать строку без всяких кастов.
И это прекрасно доказывается компилятором безо всяких бойлерплейтных [0..N] (ну, в теории: на практике, поскольку в «C++» строк нет, компилятор объёбывается).
Факт в том, что вот эти вот «[0..N]» в контексте проверок индексов не несут никакой полезной информации. Компилятор сам прекрасно знает, что если переменная используется для индексации, то её значение должно лежать в пределах [0..N). Ничего нового программист компилятору не говорит.
Более того, и не может сказать, потому что программист не может достоверно знать, что введёт пользователь. Ему, программисту, всё равно придётся явно писать «if idx < len(arr) ...» — и из этого компилятор прекрасно выводит все предикаты.
>Здесь в любом случае потребуются рантайм-проверки, либо через неявную проверку границ, либо через явный if.
Да. И компилятор нам об этом сообщит что их НУЖНО написать.
Таким образом, не дав собрать плохой код.
>И это прекрасно доказывается компилятором безо всяких бойлерплейтных [0..N] (ну, в теории: на практике, поскольку в «C++» строк нет, компилятор объёбывается).
Ок. Раз всё так прекрасно, это всё говно и бойлерплейт, непонятно почему по-прежнему десятки-сотни CVE по переполнению буфера?
> Да. И компилятор нам об этом сообщит что их НУЖНО написать.
Компилятор об этом может сообщить и без [0..N]-бойлерплейта. Он и так это знает.
> непонятно почему по-прежнему десятки-сотни CVE по переполнению буфера?
Потому что «C».
В «Java», «Python» и прочих анскильных языках переполнение буфера приведёт только к падению приложения — в самом худшем случае.
А почему конпелятор знает об этом, но не предупреждает? Видимо, разработчики посчитали, что игра не стоит свеч, а излишнее количество ворнингов сделает только хуже.
Перейдём к реальным примерам. Возьмём произвольную достаточно объёмную опенсорсную прогу и проверим: https://github.com/microsoft/vcpkg. Я прошёлся по её коду грепом findstr'ом и обнаружил занимательную вещь: индексирования массивов/векторов там практически нет, в среднем на один файл встречается по паре-тройке примитивных индексирований (вида «return &data[0]»). [To be continued…]
>Компилятор об этом может сообщить и без [0..N]-бойлерплейта. Он и так это знает.
Ничего он не знает.
Если из глубины вызовов пришёл обычный int, компилятор не может точно сказать нужна проверка или нет.
Как уже сказано ранее:
>>>Крестокомпилятор до сих пор и компайл-тайм деление на ноль отловить не всегда может, а ты замахиваешься на настолько грандиозные вещи.
>В «Java», «Python» и прочих анскильных языках переполнение буфера приведёт только к падению приложения
Есть две крайности:
1. всовывать runtime-проверки на каждом обращение.
2. не делать никаких проверок в принципе
Того что предлагаю я: сообщать об ошибке и требовать ручной каст в правильный тип, только при условнии невозможности доказательства корректности кода (индексы типа 42 по массивам известным в runtime)
То есть безопасные случаи без проверок будут работать как и раньше. А весь пользовательский ввод придётся валидировать.
> Если из глубины вызовов пришёл обычный int, компилятор не может точно сказать нужна проверка или нет.
А так из глубины вызовов придёт int[0..N] с неопределённым диапазоном, привязанным к какому-то левому массиву.
> сообщать об ошибке и требовать ручной каст в правильный тип, только при условнии невозможности доказательства корректности кода
Я предлагаю абсолютно то же самое. Только без бойлерплейта в виде int[0..N], который никакой новой информации не несёт.
> 1. всовывать runtime-проверки на каждом обращение.
Я уже показал, что, например, «gcc» может доказать, что переменная никогда не выйдет за границы массива, и выкинуть ненужные проверки. Значит, для реализации надёжной индексации достаточно развить его систему доказательств и сделать ворнинг, когда для доказательства недостаточно информации.
> Кроме вот этих мест
Зачем? Зачем? Такой вид цикла — это самый простой кейс для компилятора. Корректно вывести эти вот [0:7] ([1:7], кстати говоря) сможет даже пхп-обезьяна.
> Цикл for — самое простое, где нужна такая типизация.
Как раз в цикле for она вообще не нужна.
> Всякие find, rfind, find_first_of, find_last_of получающие индексы
А их в любом случае надо проверять в рантайме на std::string::npos. По моим экспериментам, пока что «gcc» не догадывается, что «str.find(...) != npos» эквивалентно «find(...) < str.size()». Но для доказательства конкретно этого факта никакие явные «[0..N]» не нужны.
>А их в любом случае надо проверять в рантайме на std::string::npos.
Мы уже говорили, что это должны делать паттерн-матчинг и продвинутая типизация.
Я о другом твержу.
Если мы передали в substr(int [0:N],int [0:N]) рандомный int — программа выдаёт ворнинг/ошибку
Если мы передали результат find, rfind, find_first_of, find_last_of, программа компилируется без дополнительных проверок.
>Корректно вывести эти вот [0:7] ([1:7], кстати говоря) сможет даже пхп-обезьяна.
Может пхп-обезьяна и сможет, а gcc, как видим не способен.
И clang тоже
> Мы уже говорили, что это должны делать паттерн-матчинг и продвинутая типизация.
Это в любом случае будут явные рантайм-проверки. Абсолютно в любом — пока в поиске участвуют строки, не известные на момент компиляции.
> Может пхп-обезьяна и сможет, а gcc, как видим не способен.
Значит надо выкинуть «gcc» с «clang» и написать нормальный компилятор, который будет способен. Явное обозначение [1:7] не даст компилятору никакой новой информации.
Я, кстати, за «std::array»: https://gcc.godbolt.org/z/YLySg4 (я там в main() добавил использование олгоритма, а то компилятор просто выкидывал всё нахуй).
<source>: In member function 'void Sha256Algorithm::process_full_chunk()':
<source>:62:26: warning: 'void* __builtin_memmove(void*, const void*, long unsigned int)' forming offset [32, 51] is out of the bounds [0, 32] of object 'local' with type 'std::array<unsigned int, 8>' [-Warray-bounds]
62 | local[j] = local[j - 1];
<source>:41:38: note: 'local' declared here
41 | std::array<std::uint32_t, 8> local;
| ^~~~~
Как видно, тут внутри у компилятора происходят какие-то диапазонные проверки, очень похожие на то, что ты предлагаешь реализовать явно.
>Это в любом случае будут явные рантайм-проверки. >Абсолютно в любом — пока в поиске участвуют строки, не известные на момент компиляции.
Нет. В этом вся идея. Это герметичная система, для произвольного N.
find вернёт [0,N], а substr примет этот же диапазон [0,N].
Это инвариант для строк произвольной длины.
Потому явные проверки в данном случае НЕ НУЖНЫ.
>Что вернёт find, если в строке не найдено искомой подстроки?
Уже говорили выше (https://govnokod.ru/26515#comment535141)
Если не найдено возвращается тип NotFound.
В случае если найдено: тип int[0,N-M].
Где M — длина искомой подстроки.
Это логическое продолжение идеи жёстких компайл-тайм проверок.
Значит, во время выполнения будет рантайм-проверка какого-то флага типа или чего-то подобного. Но она будет. Принципиально это ничем не отличается от текущей ситуации, когда в результате find может быть либо npos, либо [0, N - M].
>Значит, во время выполнения будет рантайм-проверка какого-то флага типа или чего-то подобного.
Смешиваются 2 разные ситуации:
* ситуация ненайденной подстрокой
* ситуация с некорректно вычисленным индексом, вылазящим за границы
>Принципиально это ничем не отличается от текущей ситуации, когда в результате find может быть либо npos, либо [0, N - M]
Отличается. Т.к. в случае когда подстрока найдена и будет дальнейший substr компилятор не даст нам вылезти за пределы строки.
А разрешит только использовать только легальные индексы с типом вмещающимся в [0, N].
>find может быть либо npos, либо [0, N - M].
Проверка индекса на npos не гарантирует, что мы не прибавим к нему чего-то лишнего и не вылезем за пределы массива.
> А разрешит только использовать только легальные индексы с типом вмещающимся в [0, N].
А зачем нам для этого явная предикатная система типов?
Компилятор и так знает, что find() может вернуть либо npos, либо [0, N - M]. Ему для этого не надо явно что-то указывать.
Компилятор и так знает, проверили ли мы результат find() на npos, или нет. Ему для этого не надо явно что-то указывать.
На основании этих джвух знаний компилятор уже может доказать, что мы помещаем в substr() валидный/невалидный индекс.
> Проверка индекса на npos не гарантирует, что мы не прибавим к нему чего-то лишнего и не вылезем за пределы массива.
А это должен гарантировать компилятор. Он знает, что если мы проверили индекс на npos, то в индексе будет [0, N - M] — потому что ничего другого там быть уже не может. Из этого он может вывести всё то же самое, что он смог бы вывести из явной предикатной системы типов.
>А зачем нам для этого явная предикатная система типов?
Она должна быть как минимум под капотом.
Нам же она нужна для определения своих типов.
Вроде typedef UserName String<[1..32]> означающего что имя пользователя содержит хотя бы один символ, но не более 32х (например у нас в бд поле nchar(32)).
Тогда пример с capitalize не будет требовать никаких доп. проверок.
Т.к. сам тип гарантирует наличие первого символа.
> Она должна быть как минимум под капотом
Разумеется, я не говорю, что предикатная система вообще не нужна — это мощный инструмент, позволяющий делать крутые вещи. Моя позиция в том, что она (в явном виде) не требуется конкретно для создания языка, в котором невозможен выход за границу массива. Я вообще против того, чтобы писать что-то, что за меня спокойно может написать компилятор.
> typedef UserName String<[1..32]>
Да, это реальный и нужный пример.
А я не говорю что эти типы должен повсеместно писать программер.
Наоборот, я повторяю что вся концепция должна быть на 90% под капотом (https://govnokod.ru/26502#comment533594).
Согласен что в большинстве случаев эти типы никак не должны себя проявлять, пока не случится ошибка. Большинству людей хватит обычного for (auto x:[0..7]).
>для создания языка, в котором невозможен выход за границу массива
Тогда непонятно как собирать вместе различные модули, связанные интерфейсами и заголовочными файлами.
В сигнатурах apiшек таки придётся их указать.
> Тогда непонятно как собирать вместе различные модули, связанные интерфейсами и заголовочными файлами.
Да, вот это реальная проблема. В этом случае без специальных сигнатур никак не обойтись.
>Компилятор и так знает, что find() может вернуть либо npos, либо [0, N - M]. Ему для этого не надо явно что-то указывать.
Проблемы начнутся при линковке модулей.
Когда некая функция описанная в .h файле принимает какой-то массив размера N, и возвращает int.
Без указания явной связи компилятор ничего не сможет сделать.
> здесь нет ни одного индексирования, которое компилятор не сможет проверить без дополнительных подсказок программиста
Компилирую. Получаю ровно 1 ворнинг, никак не связанный с кривым индексом.
$ g++ -Wall -Wextra bounds.cpp
bounds.cpp: In member function ‘void Sha256Algorithm::process_full_chunk(const std::array<unsigned char, 64>&)’:
bounds.cpp:32:66: warning: unused parameter ‘chunk’ [-Wunused-parameter]
void process_full_chunk(const std::array<uchar, chunk_size>& chunk) noexcept
Почему-то даже в самом тривиальном примере (статический массив фиксированного размера и цикл в том же скоупе) компилятор не смог найти неверную индексацию за пределами массива.
Я нигде не говорил, что современные крестовые компиляторы соответствуют моему предложению. Своим реальным примером я лишь продемонстрировал, что у них есть потенциальная возможность запилить такую систему.
К сожалению, крестовый стандарт наоборот противодействует созданию подобной надёжной системы: с формальной точки зрения, ты написал UB, а потому компилятор имеет право делать абсолютно что угодно, в том числе и молчать как рыба.
> В современных ЯВУ нет краеугольного камня для этой системы: целых типов с диапазонами.
Потому что такая система проверки индексов прекрасно работает для строк и индексов, известных во время компиляции. Как только в неё попадает рантайм — всё рушится и сводится к обычному коду.
> Любая вложенность по сути сводится к тому
Это всё частные случаи. Проблема останова никуда не девается.
> Сишники 40 лет, как Моисей, таскают за массивами их размеры и ничего.
Ну, не совсем ничего. Благодаря этому мы эти же самые 40 лет имеем максимально опасные уязвимости.
> Во всех местах придётся указывать диапазоны возвращаемых интов.
В результате для 99% методов это будет «int[0..N]» с ручными проверками. Зачем? Зачем?
> Тип индекса привязан к типу размера массива.
Мы говорим о статической или динамической типизации? Я так понял, что о статической. Тогда типы что массивов, что индексов, будут равны «*[0..N]», и смысл всей этой затеи ускользает.
Можно всё проделать гораздо проще: заставить компилятор доказывать, что любая индексация корректна. Не может доказать — показывает ошибку/предупреждение. По своей сути это ничем не будет отличаться от системы типов с диапазонами, доказывать надо будет абсолютно то же самое, зато программисту не надо будет плодить плохочитаемый бойлерплейт.
> In file pituh.koko:146:
Это не поможет, если indexOf вернёт индекс последнего элемента. Или ты предлагаешь запретить вообще любой непроверенный доступ к массиву (изменил индекс — изволь делать if idx < arr.len)?
> Вот возвращать подтипы
А это уже то, о чём гуест8 и я писали в самом начале ветке.
>Или ты предлагаешь запретить вообще любой непроверенный доступ к массиву (изменил индекс — изволь делать if idx < arr.len)?
Не совсем.
Любой вылезший за тип и недоказанный компилером.
> var i=s.indexOf("koko") //[0,N-1-4]
> //к i можно прибавить число [0,4], чтобы не вылезти за пределы.
Другой пример
var i=arr.indexOf(3) //[0,N-1]
//к i можно безопасно прибавить тип [0,0]. То есть сложить с нулём.
>Это не поможет, если indexOf вернёт индекс последнего элемента
Длина строки N.
indexOf() возвращает последний элемент N-1, это значение находится в диапазоне [0, N-1] — соответственно если индекс не менять, то по нему смело можно обращаться к массиву.
>изменил индекс — изволь делать if idx < arr.len
Не if а скорее явный clamp-каст в тип [0,N-1]
А это уже будет тормозить, потому что зерокост коллекций на горизонте не видно.
Вдобавок, в некоторых случаях это нарушает семантику. Метод, по определению возвращающий ноль или один элементов (какой-нибудь «getById»), и при этом в сигнатуре имеющий Iterable, выглядит крайне странно и запутывает читающего.
>А это уже будет тормозить, потому что зерокост коллекций на горизонте не видно.
Ээээ. Ну неправда же.
В жабе их полно. Кост у них такой же как у Optional.
> такой же как у Optional
Дык я не про жабовское анскильное гуано, а про гипотетический зерокост (в крестовом стиле) Optional с жёсткими компайл-тайм проверками (конпелятор выдаёт ошибку, если не может доказать, что на момент доступа к содержимому оно было явно проверено на существование).
> List[0,1]<Image>
А это и есть обобщённый Optional, и главный* его недостаток — необходимость многость рефакторить при изменении количества возвращаемых элементов — никуда не девается. Даже в случае с List[0,10].
А вот обобщённая коллекция потребует либо кучи, либо дополнительных проверок, что уже не зерокост и вообще тормозная питушня.
Я привёл этот пример в качестве хуёвого архитектурного решения, крайне простого в реализации, но сложного в дальнейшей поддержке.
Впрочем, возврат коллекции тут совершенно избыточен и ничем не отличается от Optional: мы в любом случае теряем информацию о том, что пошло не так. Узнать, ввёл ли пользователь неправильный пароль или неправильный логин, не получится. Разумеется, это синтетический пример, и в реальности проверку пароля следует вынести в отдельное место.
>Впрочем, возврат коллекции тут совершенно избыточен и ничем не отличается от Optional
Именно! Я о том, что в жабе уже был один тип, покрывавший все эти нужды. Зачем что-то ещё — непонятно.
А жавашкам анскильные обёрточки nullов иногда бывают нужны. Некоторые либы, например guava-кеш не переваривает nullы в качестве значений.
Но с Iterable хотя бы можно унифицировать код, и при необходимости возвращать несколько юзеров не придётся ебать мозги рефакторингом и конверсией с Optional и в глисты и коллекции.
> Возможностью положить его в некоторые мапы/кеши, которые не поддерживают null-values
Ну или так, да. Хотя, честно говоря, это выглядит как проблема в соответствующих мапах/кешах, а не в null-е.
Кстати, в «C++» нельзя положить в вектор ссылку:
int a = 42, b = 13;
std::vector<int&> vec = {a, b};
Такой код генерирует простую и понятную ошибку на жалких 20000 символов: https://pastebin.com/SGeh4X19.
Чтобы исправить это досадное упущение, в Стандарт запихнули некий «std::reference_wrapper», находящийся — что очевидно любому здравомыслящему человеку — в заголовочном файле <functional>:
int a = 42, b = 13;
std::vector<std::reference_wrapper<int>> vec = { a, b };
vec[0].get() = -1;
std::cout << a << std::endl; // -1
Optional опасен тем, что он подталкивает программиста писать говно. Точно так же, например, как пресловутое goto, которое тоже в очень редких и специфических случаях полезно и нужно, а во всех остальных приводит к нечитаемой лапше.
Optional — это инструмент, который может использовать только человек с сильной волей и железной дисциплиной. Иначе получается говно.
Значит, ты их просто не замечал. Или же тебе повезло, и другой человек понимает, что, образно выражаясь, Optional considered harmful. Или, как вариант, ты не видишь проблемы в сообщении об ошибке вида «что-то пошло не так».
Есть рациональные причины обращать повышенное внимание на методы, возвращающие Optional. Такие методы уместны тогда и только тогда, когда причина возврата null может быть только одна, и нет никаких предпосылок, что в будущем не появится ещё какой-то причины. Просто потому, что любой Optional — это потеря информации об ошибке. В случаях, когда эта информация может быть точно восстановлена (collection.find('petooh')) — Optional уместен (но всё равно остаётся проблема рефакторинга, о которой говорил дядя ПИ).
В дополнение, есть рациональные причины выкинуть Optional из новых языков программирования (о чём мы тут и развели дискуссию). Именно поэтому я за «Go».
Это ничем не отличается от ситуации c goto.
Fike # 0
хоть бы раз без ошибок пост оформил, пидор ёбаный
Janycz # 0 ⇈
3.14159265 # 0
Fike # 0 ⇈
3.14159265 # 0 ⇈
А дальше уже этот метод (без throws) используется по коду.
Janycz # 0
Я кста взглянув на код, подумал что это C#.
Вот что в нашем C# хорошо - так это то, что нам не нада писать implements, extends, throws.
Хотя я когда выполнял лабораторки в унике по Java, в throws писал throws Throwable() - а че, мне ваша ява нафиг не сдалась, но нада тупа сдать.
guest # 0 ⇈
"throws" Это checked exceptions: идея была такая: ты или возвращаешь значение, или ошибку. throws заставляет клиента провериться на ошибку. Это решение тут же все возненавидели, и потому в C# (along with Kotlin) этого уже нет.
правильнее было бы сделать монаду и паттерн матчинг, но увы
Janycz # 0 ⇈
Fike # 0 ⇈
Самое смешное, что делать внятный хендлинг ошибок в каждом методе конечно никто не будет. Поэтому гоферы без какой-либо застенчивости возвращают ошибку на уровень выше, типа там выше есть конечный юзер, пусть сам на своем сервере ебаном разбирается чойта мы забыли файл перед обновлением залочить.
https://awalterschulze.github.io/blog/monads-for-goprogrammers/bartiferr.png
3.14159265 # 0 ⇈
Go старые сишники пилили.
И всё бы ничего, да только из-за gc вся его сишность насмарку.
Janycz # 0 ⇈
admin # 0 ⇈
Janycz # 0 ⇈
guest # 0 ⇈
Так же слышал, что там нет обобщенного программирования. Это правда? Как же они делают контейнеры объектов?
Fike # 0 ⇈
да, но и еще многие языки, и даже шарп тебе без проблем соберет единый бинарник (джава сосатб), но об этом еженощно не заливают одни макаки в уши другим
> Так же слышал, что там нет обобщенного программирования. Это правда? Как же они делают контейнеры объектов?
Переписывая один и тот же код под разные типы. И это норма.
kak # 0 ⇈
Fike # 0 ⇈
ну привет
https://docs.microsoft.com/en-us/dotnet/core/deploying/deploy-with-cli#self-contained-deployment
https://github.com/dotnet/core/blob/master/Documentation/prereqs.md
тут вообще недавно постили статью, где чувак шарповую прогу ужал до восьмикилобайтного экзешника
guest # 0 ⇈
kak # 0 ⇈
Впрочем, мокрософт заботливо доставит этот рантайм через automatic updates и даже перезагрузит пека для тебя.
> all required .NET Core files to run your app but it doesn't include the native dependencies of .NET Core. These dependencies must be present on the system before the app runs.
kak # 0 ⇈
Fike # 0 ⇈
kak # 0 ⇈
Такой-то перевод стрелок. Как будто .NOT сможет работать со строкой "Hello world!" без кучи предустановленного барахла, но которое к счастью поставляется из коробки вместе с windows analytics.
guest # 0 ⇈
Да, .net можно слинковать, мы тут это обсуждали
Спольски про это давно мечтал
Fike # 0 ⇈
guest # 0 ⇈
Fike # 0 ⇈
kak # 0 ⇈
3.14159265 # 0 ⇈
Обычно занимающий сотни мегабайт.
Fike # 0 ⇈
Извините, накипело.
Janycz # 0 ⇈
x?.Some() ?? None
guest # 0 ⇈
Это если x равно нулу, а как мне узнать почему оно ему равно? как ошибку-то получить?
Fike # 0 ⇈
Janycz # 0 ⇈
Fike # 0 ⇈
guest # 0 ⇈
Что я могу сделать с питухом?
pituh.[ctrl + space]
Fike # 0 ⇈
А когда начинают совать всё подряд, их становится до пизды
guest # 0 ⇈
bormand # 0 ⇈
А что в них плохого? Это же обычные свободные функции с псевдо-ООП'шным сахарком для их вызова...
guest # 0 ⇈
Напиши мне метод, который или возвращает User или текст сообщения о причине ошибки. Причем так, чтобы клиент метода не смог получить NPE.
3.14159265 # 0 ⇈
Подтверждаю.
Причём бойлерплейтное.
Монада есть, а трансформеры не завезли.
guest # 0 ⇈
Desktop # 0 ⇈
gost # 0 ⇈
Desktop # 0 ⇈
gost # 0 ⇈
Разумеется, в некоторых случаях «Optional» может быть полезным: например, какому-нибудь методу поиска элемента в коллекции действительно удобнее возвращать «Optional». Но попытки использовать его в более сложных случаях (что-нибудь вроде «db.getUser(username, password) -> Optional<User>») приводят именно к тому, о чём я и сказал: к невразумительным ошибкам и большим сложностям в рефакторинге.
3.14159265 # 0 ⇈
Питушарская, тупая обёртка для анскилябр.
>например, какому-нибудь методу поиска элемента в коллекции действительно удобнее возвращать «Optional»
indexOf до сих пор возвращает -1. И никто не жалуется. И не городит вместо int сложных типов c позицией и флагом ошибки.
Тем более что абстракция «Optional» пока что не zero-cost.
gost # 0 ⇈
Error-prone питушня.
Optional с компайл-тайп проверками гораздо надёжнее. Что-то вроде такого псевдокода:
Разумеется, это только набросок, чтобы дать общее представление о функционале.
3.14159265 # 0 ⇈
Не-не-не-не. Вы что??? If это параша, для императивных старпёров.
Уже лет 5 как западло писать if, for, while.
Сейчас модно писать функциональненько
https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#ifPresent-java.util.function.Consumer-
>Optional с компайл-тайп проверками гораздо надёжнее.
Возможно. Только лямбды и обёртки снижают скорость кода. А пользы особой не дают.
gost # 0 ⇈
Лямбды — возможно. Обёртки в крестовом зерокост стиле — настолько несущественно и в настолько экзотических случаях, что этим можно пренебречь, поскольку выбор небезопасных интов только из-за скорости в 99.95% случаях будет классической и вредной premature optimization.
> А пользы особой не дают.
Они дают гарантию того, что полученный -1 не будет где-нибудь ниже по коду засунут в индекс. В анскильной питушне с bounds-checking это будет неожиданное исключение, а вот в крестосях — UB, что гораздо печальнее.
3.14159265 # 0 ⇈
Ну я же за Царcкий режим топлю.
В общем я считаю Optional частным видом списка из 0 или 1 элемента.
А сам список можно представить в виде массива.
>Они дают гарантию того, что полученный -1 не будет где-нибудь ниже по коду засунут в индекс.
Ну не проверять код возврата indexOf, это уж совсем для анскильных лохов.
gost # 0 ⇈
Он и есть.
> Ну не проверять код возврата indexOf, это уж совсем для анскильных лохов.
Любые программы по определению пишутся невнимательными анскильными лохами (степень анскильности, конечно, варьируется). Одна из главных задач языка программирования высокого уровня — снизить количество ошибок, вызванных анскильностью и невнимательностью. В этом смысле Optional с жёсткими компайл-тайм проверками гораздо лучше простого инта.
3.14159265 # 0 ⇈
>Они дают гарантию того, что полученный -1 не будет где-нибудь ниже по коду засунут в индекс.
Это редкая ошибка.
А вот гораздо более частая ошибка, когда к полученному в indexOf значению (предварительно прочеканному на -1) приплюсовывают что-то эдакое и снова лезут с ним в массив/строку.
И её хер обойдёшь возвращая Optionalы.
>Одна из главных задач языка программирования высокого уровня — снизить количество ошибок, вызванных анскильностью и невнимательностью.
И поэтому вообще нужны типы, которые я описывал в предыдущих дискуссиях. А именно: целые с диапазонами.
indexOf по строке должен возвращать
строго тип int[0,length-searchString.length-1].
А substrы должны принимать строго тип int[0,length-1]
И компилятор при инкременте int[0,length-1] должен возвращать тип int[1,length]. А при добавлении 42 тип будет int[42,length-41]
Соответственно при желании полезть в строку/массив с таким типом программа просто не соберётся.
gost # 0 ⇈
Да. Нужен полностью компайл-тайм Optional, а не всякая анскиллушня.
> И её хер обойдёшь возвращая Optionalы.
Очень даже обойдёшь. Нет индексов — нет ошибок с приплюсовыванием.
> indexOf по строке должен возвращать строго тип int[0,length-searchString.length-1].
А как быть со строками, которые вводит пользователь?
3.14159265 # 0 ⇈
А компилятор может считать длину строки за некий N. И типы делать [0,N-1]
var i=s.indexOf("koko") //[0,N-1-4]
//к i можно прибавить число не больше 4, чтобы не вылезти за пределы.
Как только вылазим за тип — выдавать ошибку.
>Да. Нужен полностью компайл-тайм Optional, а не всякая анскиллушня.
Да. См. ниже. Паттерн-матчинг.
gost # 0 ⇈
> Да. См. ниже. Паттерн-матчинг.
Для самых простых вещей, вроде collection.find(), это оверинжиниринг.
3.14159265 # 0 ⇈
Значит нужно допиливать автовывод типов.
>или из глубины вызовов
С индексной арифметикой, да ещё разбросанной по глубине вызовов можно страшно налажать. Уж лучше иметь ошибку компиляции, чем хрупкий код-пиздец.
В одном месте поменяли, в другом забыли. Another buffer overflow CVE.
>или из прошедшего проверку пользовательского ввода
Проверку он пройдёт только конверсией в нужный тип.
В принципе по объёму кода это одно и то же.
> При этом правила, когда компилятор может автоматически проверить индекс, а когда не может, будут настолько сложны и запутаны
Ничего сложного там нет. Просто buffer overflow говно больше никогда не скомпилится и не попадёт в продакшн.
>просто проверять индексы везде, что перфоманса не добавит.
В случае анскильного отброса с руками из жопы — безусловно.
Положительным моментом будет опять-таки то что buffer overflow говно больше никогда не скомпилится и не попадёт в продакшн.
gost # 0 ⇈
Полностью это сделать невозможно.
> buffer overflow
Про эти страшные слова забыли в любых языках с проверками границ.
> Проверку он пройдёт только конверсией в нужный тип.
Как именно? Как нужно будет переписать, например, такой код:
?
> Ничего сложного там нет.
Крестокомпилятор до сих пор и компайл-тайм деление на ноль отловить не всегда может, а ты замахиваешься на настолько грандиозные вещи.
> В случае анскильного отброса с руками из жопы — безусловно.
Неанскильный неотброс с руками из плеч тоже ничего не сможет сделать в случае, когда компилятор не сможет правильно доказать корректность индекса.
> buffer overflow говно больше никогда не скомпилится и не попадёт в продакшн
Для этого достаточно просто избавиться от индексов, как от тяжёлого наследия ассемблера.
3.14159265 # 0 ⇈
>print(string[3]); //здесь будет runtime error
Во-первых этот код — опасное говно. И хорошо, что он не скомпилится.
>когда компилятор не сможет правильно доказать корректность индекса
Не надо писать уебанский код. Я не могу представить каких-то мудрённых задач, чтобы их нельзя было решить в этой парадигме
>Про эти страшные слова забыли в любых языках с проверками границ
И расплатились 2-4 кратным снижением пирфоманса.
gost # 0 ⇈
Хорошо, давай так:
Это достаточно реальный пример. Как его переписать так, чтобы
>>> buffer overflow говно больше никогда не скомпилится и не попадёт в продакшн
?
> И расплатились 2-4 кратным снижением пирфоманса.
Даже в теории, даже для самого плохого случая вида «for i in 0..len(arr): arr[i] += 1» никаких x2 и в помине не будет.
3.14159265 # 0 ⇈
Где-то на ГК неоднократно публиковались эти цифры.
Там в сишке включали какой-то режим bounds checking и емнип пирфоманс садился в 2-4 раза.
Я сам охуел когда увидел.
>def capitalize(string):
В этом коде в любом случае придётся написать if string.len>0
Теперь наш capitalize не принимает строки нулевой длины.
И проблема перезжает в вызов:
gost # 0 ⇈
Очевидно, это был простой наброс на хреновый bounds checking. С нормальным чекером и нормальным компилятором, например, такого точно не будет.
Реальный пример: https://gcc.godbolt.org/z/DdP-Qj. «Gcc» достаточно умён, чтобы доказать нинужность лишних проверок границ. В данных случаях получился идеальный зерокост.
> typedef NotEmpty
Не нужно, это лишнее усложнение примеров. Я хочу понять, как программист должен будет говорить компилятору, что данный индекс проверен и точно не вылезет за границы рантайм-массива/строки.
> //здесь оно отвалится
Нужно сделать так, чтобы не отвалилось.
3.14159265 # 0 ⇈
Это никак невозможно, если придёт пустая строка.
> string[0] = to_upper(string[0])
Либо отвалится здесь, с out of bounds.
>Я хочу понять, как программист должен будет говорить компилятору, что данный индекс проверен и точно не вылезет за границы рантайм-массива/строки.
Явно сконвертировать этот индекс в тип [0..N].
Если конверсия будет неудачной — произойдёт runtime ошибка.
Но т.к. программист уверен в годности индекса, в теории ошибок быть не должно.
gost # 0 ⇈
Я написал код с ошибкой (выхода за границы массива), которую предложил исправить.
>>> Я хочу понять, как программист должен будет говорить компилятору, что данный индекс проверен и точно не вылезет за границы рантайм-массива/строки.
Просто переведи на «систему типов с диапазонами»:
3.14159265 # 0 ⇈
В случае неявных преобразований типов вторую строчку можно убрать и мы автоматом получим язык с runtime bounds checking.
gost # 0 ⇈
Стоп, стоп, стоп. Это статический или динамический тип?
Ну и в любом случае, для правильного перевода не хватает ручной проверки на пустоту строки, поскольку приведённый мной в последнем комментарии отрывок на ней не упадёт.
3.14159265 # 0 ⇈
Такое можно накрестячить даже на современных шаблонах.
>Ну и в любом случае, для правильного перевода не хватает ручной проверки на пустоту строки,
Ага. Ниже пример.
gost # 0 ⇈
Тем не менее, я всё ещё не вижу никаких реальных преимуществ такой системы даже по сравнению с обычным неявным bounds checking. Абсолютное большинство индексаций в реальном коде происходит на рантайм-массивах либо же с рантайм-индексами. Доступ по компайл-тайм-вычислимому индексу к компайл-тайм-вычислимому массиву, который такая система может проверить в компайл-тайме, крайне редок.
Всё остальное переходит в рантайм-проверки («cast()»).
Более того, как я показывал выше (https://gcc.godbolt.org/z/DdP-Qj), мощные компиляторы вполне способны проводить индексные доказательства и выкидывать лишние проверки даже без диапазонных (хотя, конечно, в данном случае правильнее будет «предикатных») типов.
3.14159265 # 0 ⇈
Нет. cast нужен только для абсолютных чисел (42) по рандомным строкам полученным извне. Поскольку юзер всегда может ввести строку длиной меньше 42.
В cast не переходят
Srting x="kokoko";
x[3];
В cast не переходят штуки с indexOf/substring, завязанные на строку.
>Абсолютное большинство индексаций в реальном коде происходит на рантайм-массивах либо же с рантайм-индексами.
Поправочка. На рантайм-массивах либо же вместе с рантайм-индексами.
А тип рантайм-индекса, у нас всегда завязан на рантайм-размер рантайм-массива.
Обычная задача парсинга: найти начало подстроки, найти конец подстроки. Вырезать нужное.
indexOfы будут возвращать тип [O..N] которым можно будет индексировать строку без всяких кастов.
> мощные компиляторы вполне способны проводить индексные доказательства и выкидывать лишние проверки
Даже ява с 7ой версии научилась.
Но для примера сложнее тестового, компилер без подсказок в виде предлагаемой диапазонной типизации может обосраться с их выпиливанием.
gost # 0 ⇈
А также для индексов, полученных из рантайма.
> Поправочка.
Неверная поправка. Реальный пример:
И совершенно неважно, известно ли содержимое pages на этапе компиляции. Здесь в любом случае потребуются рантайм-проверки, либо через неявную проверку границ, либо через явный if.
> Обычная задача парсинга: найти начало подстроки, найти конец подстроки. Вырезать нужное.
> indexOfы будут возвращать тип [O..N] которым можно будет индексировать строку без всяких кастов.
И это прекрасно доказывается компилятором безо всяких бойлерплейтных [0..N] (ну, в теории: на практике, поскольку в «C++» строк нет, компилятор объёбывается).
Факт в том, что вот эти вот «[0..N]» в контексте проверок индексов не несут никакой полезной информации. Компилятор сам прекрасно знает, что если переменная используется для индексации, то её значение должно лежать в пределах [0..N). Ничего нового программист компилятору не говорит.
Более того, и не может сказать, потому что программист не может достоверно знать, что введёт пользователь. Ему, программисту, всё равно придётся явно писать «if idx < len(arr) ...» — и из этого компилятор прекрасно выводит все предикаты.
3.14159265 # 0 ⇈
>Здесь в любом случае потребуются рантайм-проверки, либо через неявную проверку границ, либо через явный if.
Да. И компилятор нам об этом сообщит что их НУЖНО написать.
Таким образом, не дав собрать плохой код.
>И это прекрасно доказывается компилятором безо всяких бойлерплейтных [0..N] (ну, в теории: на практике, поскольку в «C++» строк нет, компилятор объёбывается).
Ок. Раз всё так прекрасно, это всё говно и бойлерплейт, непонятно почему по-прежнему десятки-сотни CVE по переполнению буфера?
gost # 0 ⇈
Компилятор об этом может сообщить и без [0..N]-бойлерплейта. Он и так это знает.
> непонятно почему по-прежнему десятки-сотни CVE по переполнению буфера?
Потому что «C».
В «Java», «Python» и прочих анскильных языках переполнение буфера приведёт только к падению приложения — в самом худшем случае.
А почему конпелятор знает об этом, но не предупреждает? Видимо, разработчики посчитали, что игра не стоит свеч, а излишнее количество ворнингов сделает только хуже.
Перейдём к реальным примерам. Возьмём произвольную достаточно объёмную опенсорсную прогу и проверим: https://github.com/microsoft/vcpkg. Я прошёлся по её коду грепом findstr'ом и обнаружил занимательную вещь: индексирования массивов/векторов там практически нет, в среднем на один файл встречается по паре-тройке примитивных индексирований (вида «return &data[0]»). [To be continued…]
3.14159265 # 0 ⇈
Ничего он не знает.
Если из глубины вызовов пришёл обычный int, компилятор не может точно сказать нужна проверка или нет.
Как уже сказано ранее:
>>>Крестокомпилятор до сих пор и компайл-тайм деление на ноль отловить не всегда может, а ты замахиваешься на настолько грандиозные вещи.
>В «Java», «Python» и прочих анскильных языках переполнение буфера приведёт только к падению приложения
Есть две крайности:
1. всовывать runtime-проверки на каждом обращение.
2. не делать никаких проверок в принципе
Того что предлагаю я: сообщать об ошибке и требовать ручной каст в правильный тип, только при условнии невозможности доказательства корректности кода (индексы типа 42 по массивам известным в runtime)
То есть безопасные случаи без проверок будут работать как и раньше. А весь пользовательский ввод придётся валидировать.
gost # 0 ⇈
А так из глубины вызовов придёт int[0..N] с неопределённым диапазоном, привязанным к какому-то левому массиву.
> сообщать об ошибке и требовать ручной каст в правильный тип, только при условнии невозможности доказательства корректности кода
Я предлагаю абсолютно то же самое. Только без бойлерплейта в виде int[0..N], который никакой новой информации не несёт.
> 1. всовывать runtime-проверки на каждом обращение.
Я уже показал, что, например, «gcc» может доказать, что переменная никогда не выйдет за границы массива, и выкинуть ненужные проверки. Значит, для реализации надёжной индексации достаточно развить его систему доказательств и сделать ворнинг, когда для доказательства недостаточно информации.
gost # 0 ⇈
Как видно, здесь нет ни одного индексирования, которое компилятор не сможет проверить без дополнительных подсказок программиста.
3.14159265 # 0 ⇈
А они тут и не нужны. Кроме вот этих мест:
Хотя с такой системой типов можно просто писать auto и компилер сам догадается. Цикл for — самое простое, где нужна такая типизация.
Подсказки программиста нужны в случае работы с массивом через функции.
Всякие find, rfind, find_first_of, find_last_of получающие индексы
А за ним substr, принимающие эти индексы.
Как С++ компилятор что-то может доказать в этом случае?
У меня godbolt висит, пока не могу продемонстрировать.
gost # 0 ⇈
Зачем? Зачем? Такой вид цикла — это самый простой кейс для компилятора. Корректно вывести эти вот [0:7] ([1:7], кстати говоря) сможет даже пхп-обезьяна.
> Цикл for — самое простое, где нужна такая типизация.
Как раз в цикле for она вообще не нужна.
> Всякие find, rfind, find_first_of, find_last_of получающие индексы
А их в любом случае надо проверять в рантайме на std::string::npos. По моим экспериментам, пока что «gcc» не догадывается, что «str.find(...) != npos» эквивалентно «find(...) < str.size()». Но для доказательства конкретно этого факта никакие явные «[0..N]» не нужны.
3.14159265 # 0 ⇈
Мы уже говорили, что это должны делать паттерн-матчинг и продвинутая типизация.
Я о другом твержу.
Если мы передали в substr(int [0:N],int [0:N]) рандомный int — программа выдаёт ворнинг/ошибку
Если мы передали результат find, rfind, find_first_of, find_last_of, программа компилируется без дополнительных проверок.
>Корректно вывести эти вот [0:7] ([1:7], кстати говоря) сможет даже пхп-обезьяна.
Может пхп-обезьяна и сможет, а gcc, как видим не способен.
И clang тоже
gost # 0 ⇈
Это в любом случае будут явные рантайм-проверки. Абсолютно в любом — пока в поиске участвуют строки, не известные на момент компиляции.
> Может пхп-обезьяна и сможет, а gcc, как видим не способен.
Значит надо выкинуть «gcc» с «clang» и написать нормальный компилятор, который будет способен. Явное обозначение [1:7] не даст компилятору никакой новой информации.
gost # 0 ⇈
Как видно, тут внутри у компилятора происходят какие-то диапазонные проверки, очень похожие на то, что ты предлагаешь реализовать явно.
3.14159265 # 0 ⇈
Нет. В этом вся идея. Это герметичная система, для произвольного N.
find вернёт [0,N], а substr примет этот же диапазон [0,N].
Это инвариант для строк произвольной длины.
Потому явные проверки в данном случае НЕ НУЖНЫ.
>Я, кстати, за «std::array»
Я тоже 🙂
https://govnokod.ru/26515#comment535136
Я же говорю: кресты уже довольно близки к такой типизации. Думаю на шаблонах можно сделать эти диапазоны. Для статических строк уж точно.
gost # 0 ⇈
3.14159265 # 0 ⇈
Уже говорили выше (https://govnokod.ru/26515#comment535141)
Если не найдено возвращается тип NotFound.
В случае если найдено: тип int[0,N-M].
Где M — длина искомой подстроки.
Это логическое продолжение идеи жёстких компайл-тайм проверок.
gost # 0 ⇈
3.14159265 # 0 ⇈
Смешиваются 2 разные ситуации:
* ситуация ненайденной подстрокой
* ситуация с некорректно вычисленным индексом, вылазящим за границы
>Принципиально это ничем не отличается от текущей ситуации, когда в результате find может быть либо npos, либо [0, N - M]
Отличается. Т.к. в случае когда подстрока найдена и будет дальнейший substr компилятор не даст нам вылезти за пределы строки.
А разрешит только использовать только легальные индексы с типом вмещающимся в [0, N].
>find может быть либо npos, либо [0, N - M].
Проверка индекса на npos не гарантирует, что мы не прибавим к нему чего-то лишнего и не вылезем за пределы массива.
gost # 0 ⇈
А зачем нам для этого явная предикатная система типов?
Компилятор и так знает, что find() может вернуть либо npos, либо [0, N - M]. Ему для этого не надо явно что-то указывать.
Компилятор и так знает, проверили ли мы результат find() на npos, или нет. Ему для этого не надо явно что-то указывать.
На основании этих джвух знаний компилятор уже может доказать, что мы помещаем в substr() валидный/невалидный индекс.
> Проверка индекса на npos не гарантирует, что мы не прибавим к нему чего-то лишнего и не вылезем за пределы массива.
А это должен гарантировать компилятор. Он знает, что если мы проверили индекс на npos, то в индексе будет [0, N - M] — потому что ничего другого там быть уже не может. Из этого он может вывести всё то же самое, что он смог бы вывести из явной предикатной системы типов.
3.14159265 # 0 ⇈
Она должна быть как минимум под капотом.
Нам же она нужна для определения своих типов.
Вроде typedef UserName String<[1..32]> означающего что имя пользователя содержит хотя бы один символ, но не более 32х (например у нас в бд поле nchar(32)).
Тогда пример с capitalize не будет требовать никаких доп. проверок.
Т.к. сам тип гарантирует наличие первого символа.
gost # 0 ⇈
Разумеется, я не говорю, что предикатная система вообще не нужна — это мощный инструмент, позволяющий делать крутые вещи. Моя позиция в том, что она (в явном виде) не требуется конкретно для создания языка, в котором невозможен выход за границу массива. Я вообще против того, чтобы писать что-то, что за меня спокойно может написать компилятор.
> typedef UserName String<[1..32]>
Да, это реальный и нужный пример.
3.14159265 # 0 ⇈
Наоборот, я повторяю что вся концепция должна быть на 90% под капотом (https://govnokod.ru/26502#comment533594).
Согласен что в большинстве случаев эти типы никак не должны себя проявлять, пока не случится ошибка. Большинству людей хватит обычного for (auto x:[0..7]).
>для создания языка, в котором невозможен выход за границу массива
Тогда непонятно как собирать вместе различные модули, связанные интерфейсами и заголовочными файлами.
В сигнатурах apiшек таки придётся их указать.
gost # 0 ⇈
Да, вот это реальная проблема. В этом случае без специальных сигнатур никак не обойтись.
3.14159265 # 0 ⇈
Проблемы начнутся при линковке модулей.
Когда некая функция описанная в .h файле принимает какой-то массив размера N, и возвращает int.
Без указания явной связи компилятор ничего не сможет сделать.
3.14159265 # 0 ⇈
У меня 9ый gcc, и 9й шланг.
Никаких ворнингов они не выдают.
> очень похожие на то, что ты предлагаешь реализовать явно
То есть тот ворнгинг самый писк прогресса, который gcc начал поддерживать только в trunke. Раньше такого не было.
https://gcc.godbolt.org/z/juLVXg
gost # 0 ⇈
3.14159265 # 0 ⇈
https://xkcd.com/927
Нужно быть реалистом.
Но хорошо что Аллах услышал мои молитвы, и они наконец-то начали пилить range проверки, пусть даже под капотом.
gost # 0 ⇈
> и они наконец-то начали пилить range проверки, пусть даже под капотом.
Вот, я тоже целиком и полностью за это.
3.14159265 # 0 ⇈
https://ideone.com/d2i4Ft
> здесь нет ни одного индексирования, которое компилятор не сможет проверить без дополнительных подсказок программиста
Компилирую. Получаю ровно 1 ворнинг, никак не связанный с кривым индексом.
Почему-то даже в самом тривиальном примере (статический массив фиксированного размера и цикл в том же скоупе) компилятор не смог найти неверную индексацию за пределами массива.
Какой анскилл )))
gost # 0 ⇈
К сожалению, крестовый стандарт наоборот противодействует созданию подобной надёжной системы: с формальной точки зрения, ты написал UB, а потому компилятор имеет право делать абсолютно что угодно, в том числе и молчать как рыба.
gost # 0 ⇈
3.14159265 # 0 ⇈
gost # 0 ⇈
BECEHHuu_nemyx # 0 ⇈
MAuCKuu_nemyx # 0 ⇈
BECEHHuu_nemyx # 0 ⇈
Не знаешь, почему тут так тихо?
KOPOHABuPYC # 0 ⇈
3.14159265 # 0 ⇈
А с ifом наверное будет так:
3.14159265 # 0 ⇈
3.14159265 # 0 ⇈
Проблема в другом.
В современных ЯВУ нет краеугольного камня для этой системы: целых типов с диапазонами.
Остальное дело техники.
Любая вложенность по сути сводится к тому что на уровне каждой функции мы передаём вниз аргументом строку, получаем связанный индекс.
Или передаём связанный индекс и строку.
int[0..N] indexOf(string[0..N] str);
substring(string[0..N] str, int[0..N] from);
Сишники 40 лет, как Моисей, таскают за массивами их размеры и ничего.
В крестах тоже везде торчит размер std:array<int,size_t>.
gost # 0 ⇈
Потому что такая система проверки индексов прекрасно работает для строк и индексов, известных во время компиляции. Как только в неё попадает рантайм — всё рушится и сводится к обычному коду.
> Любая вложенность по сути сводится к тому
Это всё частные случаи. Проблема останова никуда не девается.
> Сишники 40 лет, как Моисей, таскают за массивами их размеры и ничего.
Ну, не совсем ничего. Благодаря этому мы эти же самые 40 лет имеем максимально опасные уязвимости.
3.14159265 # 0 ⇈
Это да. Однако я имел ввиду проблему синтаксиального оверхеда подобной затеи. Во всех местах придётся указывать диапазоны возвращаемых интов.
>Как только в неё попадает рантайм — всё рушится и сводится к обычному коду.
Ничего не рушится. Тип индекса привязан к типу размера массива. Для произвольных N. Неважно какой длины строка.
Подобное предикативное доказательство проделывают в продвинутых функциональных языках типа. wvxvw очень давно пояснял.
gost # 0 ⇈
В результате для 99% методов это будет «int[0..N]» с ручными проверками. Зачем? Зачем?
> Тип индекса привязан к типу размера массива.
Мы говорим о статической или динамической типизации? Я так понял, что о статической. Тогда типы что массивов, что индексов, будут равны «*[0..N]», и смысл всей этой затеи ускользает.
Можно всё проделать гораздо проще: заставить компилятор доказывать, что любая индексация корректна. Не может доказать — показывает ошибку/предупреждение. По своей сути это ничем не будет отличаться от системы типов с диапазонами, доказывать надо будет абсолютно то же самое, зато программисту не надо будет плодить плохочитаемый бойлерплейт.
3.14159265 # 0 ⇈
И соответственно ifPresent с ебаными лямбдами нахуй не нужен.
Есть же (ну или скоро будет) в шарпе/яве/крестах паттерн матчинг.
https://openjdk.java.net/jeps/305
Вот возвращать подтипы
А Optional — говнище ебаное.
gost # 0 ⇈
Это не поможет, если indexOf вернёт индекс последнего элемента. Или ты предлагаешь запретить вообще любой непроверенный доступ к массиву (изменил индекс — изволь делать if idx < arr.len)?
> Вот возвращать подтипы
А это уже то, о чём гуест8 и я писали в самом начале ветке.
3.14159265 # 0 ⇈
Не совсем.
Любой вылезший за тип и недоказанный компилером.
> var i=s.indexOf("koko") //[0,N-1-4]
> //к i можно прибавить число [0,4], чтобы не вылезти за пределы.
Другой пример
var i=arr.indexOf(3) //[0,N-1]
//к i можно безопасно прибавить тип [0,0]. То есть сложить с нулём.
>Это не поможет, если indexOf вернёт индекс последнего элемента
Длина строки N.
indexOf() возвращает последний элемент N-1, это значение находится в диапазоне [0, N-1] — соответственно если индекс не менять, то по нему смело можно обращаться к массиву.
>изменил индекс — изволь делать if idx < arr.len
Не if а скорее явный clamp-каст в тип [0,N-1]
3.14159265 # 0 ⇈
Плюс это расширябельно. Завтра могут быть 2 картинки с одинаковым именем, и все вызовы метода придётся переписывать.
Хипстеры тоже будут довольны:
gost # 0 ⇈
Вдобавок, в некоторых случаях это нарушает семантику. Метод, по определению возвращающий ноль или один элементов (какой-нибудь «getById»), и при этом в сигнатуре имеющий Iterable, выглядит крайне странно и запутывает читающего.
3.14159265 # 0 ⇈
Ээээ. Ну неправда же.
В жабе их полно. Кост у них такой же как у Optional.
Навскидку:
https://docs.oracle.com/javase/7/docs/api/java/util/Collections.html#singletonList(T)
https://docs.oracle.com/javase/7/docs/api/java/util/Collections.html#singletonList(T)
gost # 0 ⇈
Дык я не про жабовское анскильное гуано, а про гипотетический зерокост (в крестовом стиле) Optional с жёсткими компайл-тайм проверками (конпелятор выдаёт ошибку, если не может доказать, что на момент доступа к содержимому оно было явно проверено на существование).
3.14159265 # 0 ⇈
Ну в крестовом стиле и гипотетические коллекции могут быть зеро-кост.
Горячо любимый мною std::array довольно близок к этому.
gost # 0 ⇈
А это и есть обобщённый Optional, и главный* его недостаток — необходимость многость рефакторить при изменении количества возвращаемых элементов — никуда не девается. Даже в случае с List[0,10].
А вот обобщённая коллекция потребует либо кучи, либо дополнительных проверок, что уже не зерокост и вообще тормозная питушня.
admin # 0 ⇈
Перепесал, проверь.
3.14159265 # 0 ⇈
Но я уже сказал выше:
https://govnokod.ru/26515#comment535130
>А сам список можно представить в виде массива.
Впрочем for~each и на массивах работает
3.14159265 # 0 ⇈
Ну блядь верни List, Collection, Iterable наконец. С одним элементом или пустой.
Не хочу, хочу жрать «Optional»
Уже есть все нужные абстракции.
Iterable — тот же ленивый список из Хасцеля. С поддержкой for~each из коробки.
gost # 0 ⇈
Впрочем, возврат коллекции тут совершенно избыточен и ничем не отличается от Optional: мы в любом случае теряем информацию о том, что пошло не так. Узнать, ввёл ли пользователь неправильный пароль или неправильный логин, не получится. Разумеется, это синтетический пример, и в реальности проверку пароля следует вынести в отдельное место.
3.14159265 # 0 ⇈
Именно! Я о том, что в жабе уже был один тип, покрывавший все эти нужды. Зачем что-то ещё — непонятно.
А жавашкам анскильные обёрточки nullов иногда бывают нужны. Некоторые либы, например guava-кеш не переваривает nullы в качестве значений.
Но с Iterable хотя бы можно унифицировать код, и при необходимости возвращать несколько юзеров не придётся ебать мозги рефакторингом и конверсией с Optional и в глисты и коллекции.
gost # 0 ⇈
guest # 0 ⇈
gost # 0 ⇈
3.14159265 # 0 ⇈
Возможностью положить его в некоторые мапы/кеши, которые не поддерживают null-values.
А реально ничем.
gost # 0 ⇈
Ну или так, да. Хотя, честно говоря, это выглядит как проблема в соответствующих мапах/кешах, а не в null-е.
Кстати, в «C++» нельзя положить в вектор ссылку:
Такой код генерирует простую и понятную ошибку на жалких 20000 символов: https://pastebin.com/SGeh4X19.
Чтобы исправить это досадное упущение, в Стандарт запихнули некий «std::reference_wrapper», находящийся — что очевидно любому здравомыслящему человеку — в заголовочном файле <functional>:
Удобно, правда?
MAPTOBCKuu_nemyx # 0 ⇈
3.14159265 # 0 ⇈
Пастбин не открылся. Но поверю на слово 🙂
Какой пиздец )))
PS Воистину пиздец:
KOPOHABuPYC # 0 ⇈
Desktop # 0 ⇈
Не ебашьте Optional там, где вам важна ошибка
gost # 0 ⇈
Optional опасен тем, что он подталкивает программиста писать говно. Точно так же, например, как пресловутое goto, которое тоже в очень редких и специфических случаях полезно и нужно, а во всех остальных приводит к нечитаемой лапше.
Optional — это инструмент, который может использовать только человек с сильной волей и железной дисциплиной. Иначе получается говно.
Desktop # 0 ⇈
Ну так нахуй петухов, в общем-то.
gost # 0 ⇈
> Ну так нахуй петухов, в общем-то.
До тех пор, пока тебе не придётся иметь дело с кодом, написанным другим человеком.
Desktop # 0 ⇈
gost # 0 ⇈
Desktop # 0 ⇈
gost # 0 ⇈
В дополнение, есть рациональные причины выкинуть Optional из новых языков программирования (о чём мы тут и развели дискуссию). Именно поэтому я за «Go».
Это ничем не отличается от ситуации c goto.
phpBidlokoder2 # 0
Janycz # 0 ⇈
phpBidlokoder2 # 0
KOPOHABuPYC # 0 ⇈