Pull to refresh

Comments 158

а автор и не шутит, он серьезно так думает:) В этом и есть весь юмор статьи
UFO just landed and posted this here
По поводу тона, да… Наверное, переборщил. Хотелось развлечь и развлечься. И нет, я не против нового. Наоборот, я часто сам выступаю в роли тех, кого критикую в тексте. Да, такое тоже бывает )
UFO just landed and posted this here
Неприязни нет, есть большие сомнения. Как и в отношении лямбд, корутин и многого другого. Просто сомнения. Это не мешает мне всем этим пользоваться.
Дело в том, что это такие вещи, которые надо использовать очень аккуратно, важно не переборщить, не перейти границу. И, если я, скажем, могу для себя такую границу худо-бедно прочертить, то обязательно найдется тот, кто этого сделать не сможет и зайдет слишком далеко. К сожалению, сталкиваться с этим приходится гораздо чаще, чем хотелось бы.
UFO just landed and posted this here
Но кто будет добавлять механизмы по вывиливанию нула из твоего текущего старого проекта?

А зачем?
Если старый проект нет необходимости менять, то пускай там живет этот нулл. А если появился повод отредактировать, то можно даже руками Option вкрутить.


Ну допустим выпилите вы нулл из проекта. Пусть даже автоматически. Можете ли вы дать гарантию, что программа будет выполняться точно так же, как раньше?
Штуки вроде Option в языках вроде java приводят к дополнительной аллокации объекта, а это не самая дешевая операция. Чуть больше мусора в куче, чуть больше работы для GC. Но в целом программа уже не будет такой же.

UFO just landed and posted this here
Штуки вроде Option в языках вроде java приводят к дополнительной аллокации объекта, а это не самая дешевая операция.

Зависит от того, насколько умный компилятор.


Я недавно писал код, который выглядел как


msum [ lookup elem arr | arr <- arrays ]

где lookup возвращает Maybe чётотам, и весь код имеет смысл «сделай lookup элемента в каждом массиве из arr, и верни первый найденный, или Nothing, если вообще нигде ничего не нашлось». Если посмотреть в тот код, который генерится после всех оптимизаций, то промежуточных Maybe от лукапов нет, компилятор их эффективно удаляет.


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

Насколько точно так же? Включая неухудшение и неулучшение производительности?

Включая неухудшение и неулучшение производительности?

Да, включая нефункциональные требования и в частности производительность.

В вопросе с null, как говориться, «смешались в кучу кони люди». Null возник в момент, когда ссылка на объект перестала интерпретироваться как число. Он просто обозначает, что ваша переменная ни на что сейчас не ссылается. Большинство претензий у разработчиков не к null, а к NPE (или NRE). И, если я не ошибаюсь, именно их появление в языке называют ошибкой.

Все пилюли так или иначе связаны со способами проверки на null. Но кажется, что проблема раздута, нет ни каких доказательств того, что эти пилюли действительно увеличивают производительность разработчика. На конференции Joker лет пять назад сотрудник JetBrains рассказывал, что они потратили сотни человеко-часов на добавление в код Idea аннотаций Nullable и NotNull. И он честно показал метрики по багам связанным с NPE за некоторый период до и после. Расхождение было в пределах погрешности: ошибок как было не много, так и осталось. Лучше бы он про них не рассказывал.
UFO just landed and posted this here
UFO just landed and posted this here
Я не готов сказать за корутины, среди механизмов многопоточности сложно выделить лучшее решение.

STM, конечно же, когда топовая производительность не сильно нужна, и всякая там локфри-ерунда, когда она нужна, но настолько топовая производительность нужна на самом деле довольно редко.


Кстати, wetnose, что про software transactional memory думаете?


Замыкания совсем другая история.

Почему?

wait-free вполне себе нужен, когда идёт активная коммуникация между потоками. Особенно, когда это корутины в тредпуле. Возьмёт такая корутина мьютекс, да как уснёт и всё, приехали.

Это просто означает, что нельзя засыпать внутри критических секций. Причём тут wait-free — не очень понятно.

"Нельзя" — это полагаться на человеческий фактор. Предпочтительнее всё же, когда "невозможно". wait-free как раз позволяет обойтись вообще без критических секций и связанных с ними рисков и тормозов.

Вот только:


Во-первых, wait-free алгоритмы, как правило, довольно сложны — а потому куда больше подвержены человеческому фактору нежели критические секции.


Во-вторых, даже при использовании wait-free алгоритмов вам всё равно нужно следить чтобы в программе не было критических секций вокруг точек засыпания сопрограммы.


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

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


  2. Если не использовать критические секции — за ними и не надо следить.


  3. Какие языки и что именно проверяют?


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

Вот только атомарность не компонуется, и вызов нескольких wait-free алгоритмов корректным wait-free алгоритмом может уже и не являться.


Если не использовать критические секции — за ними и не надо следить.

"Не использовать" — это полагаться на человеческий фактор.


Какие языки и что именно проверяют?

Ну вот Rust проверяет:


future returned by bar is not Send
  1. wait-free не про атомарность, а про отсутствие конкуренции за ресурсы. Так что всё прекрасно компонуется.


  2. Линтер в помощь.


  3. Спец проверка для проблемы, которая есть только у мьютексов — это, конечно, замечательно, но у wait-free данной проблемы нет вовсе.



Ну, вот пример корректного wait-free кода с сотней корутин в тредпуле:


static auto produce(Output!long numbers)
{
    foreach (n; 10000000.iota)
        numbers.put(n);
}

static auto consume(Output!long sums, Input!long numbers)
{
    long s;
    foreach (n; numbers)
        s += n;
    sums.put(s);
}

        Output!long numbers;
        Input!long sums;

        foreach (t; 100.iota)
            sums.pair.go!consume(numbers.pair);
        numbers.go!produce;

        long total;
        foreach (s; sums)
            total += s;

Ну да, этот частный случай wait-free каналов компонуется. Однако, это не делает компонующимися произвольные wait-free алгоритмы. Кстати, насколько я знаю у wait-free каналов есть проблема с чтением сразу из нескольких каналов.


И да, если уж вы заговорили об архитектуре "каналы и корутины" — то все мьютексы/критические секции прекрасно инкапсулируются внутри реализации каналов. К примеру, если заменить все каналы в вашем коде на каналы с блокировками — вы этого, скорее всего, вообще не заметите (пока не попытаетесь запустить программу на процессоре со слишком большим числом ядер).

Либо приведите пример, где что-то там не скомпануется, либо не сотрясайте интернет попусту.


Нет с этим проблем — обычный round-robin.


Ну да, кроме тормозов не замечу. Вы что доказать-то пытаетесь тут?

Рассмотрим операции чтения и записи разделяемой переменной. При наличии правильных барьеров они являются вполне себе wait-free алгоритмами.


А теперь попробуйте на основе этих операций увеличить переменную на 1 без гонок.


Вы что доказать-то пытаетесь тут?

Пытаюсь доказать, что "возьмёт такая корутина мьютекс, да как уснёт" не является той проблемой, для решения которой нужны wait-free алгоритмы.

В случае wait-free это будет 2 разные переменные в разных кеш-линиях. Значение счётчика — их сумма.


А всё, что было до "особенно" проигнорировали, ясно.

В случае wait-free это будет 2 разные переменные в разных кеш-линиях. Значение счётчика — их сумма.

Но это решение не является композицией исходных операций.


А всё, что было до "особенно" проигнорировали, ясно.

Ничего я не игнорировал.

У вас не композиция, а смешивание получилось.

Я же про STM говорил, причём тут мьютексы?

UFO just landed and posted this here
Замыкания совсем другая история.
Почему?

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


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

UFO just landed and posted this here
У Вас я вижу

далее текст


Хм, действительно с markdown сейчас проблема.

но blockquote сейчас-то в превью показывает нормально.
как в js некоторые любят

Не, ну если смотреть, как в js… Короче, ИМХО это так себе аргумент против замыканий (но вполне себе аргумент против js).


Механизм, который прозволит во время компиляции проверить что все инициализируется в нужной последовательности. Я такого механизма еще не видел. Буду рад, если кто-то покажет.

Растаманы говорят, что правил лексического скоупинга достаточно. Мне из моего хаскеля или идриса тоже норм.

UFO just landed and posted this here
Проблема в том, что ею в замыканиях будут редко пользоваться.

Какой именно "ею" будут редко пользоваться?

UFO just landed and posted this here

А эта фича опциональной и не является.

Разработчики делятся на 2 лагеря — которые понимают что происходит, и которые не понимают. И я ни разу не видел, чтобы первые смогли обьяснить как читать код для вторых.

Согласно моим наблюдениям, разработчики из второго лагеря — это либо начинающие программисты, которые мыслят исключительно глобальными переменными (и у них не только с замыканиями, у них и с ООП, и даже со структурами данных проблемы) — либо те, кто по какой-то причине не хотят понимать как они работают.

UFO just landed and posted this here
файла на сотню строк, который состоит из 60-80 лямбд и замыканий

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


С таким же успехом можно составить файл на 100 строк с 60-80 условными операторами, а потом всем рассказывать что условный оператор — плохо.

UFO just landed and posted this here

Ну или для замкнутых переменных можно использовать односимволный префикс типа $name.

UFO just landed and posted this here

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

UFO just landed and posted this here

Кстати, давать имена скоупам — не самая плохая идея.

UFO just landed and posted this here

Ну вот ниже я привёл самую громоздкую конструкцию из замыканий которую я смог придумать — декоратор, который превращает функцию в декоратор:


function declareWrapper(len) {
  function (wrapper) {
    return function decoratorFactory(...config) {
      config.length = len;

      return function decorator(fn) {
        return function wrappedFunction(...args) {
          return wrapper(...config, args2 => fn(...args2), args);
        }
      }
    }
  }
}

// использование:

@declareWrapper(1)
function withLog(name, fn, args) {
    console.log(`${name} enter`, args);
    const ret = fn(args);
    console.log(`${name} exit`, ret);
    return ret;
}

@withLog("foo")
function foo(x) {
    return x+1;
}

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

UFO just landed and posted this here

На мой взгляд, ваш тон — тон гомеопата. Гомеопатия не отрицает эффективность проверенных препаратов, но предлагает производить с ними нелепые манипуляции, считая, что эффект препарата с доказанной эффективностью переносится на молекулы воды, которые находились рядом с этим препаратом.
Аналогично, вы критикуете языки, но не доказываете, что ваши претензии являются значимыми. Например, если язык скрывает часть информации, достигая лаконичности, вы относите это к критическим недостаткам, которые приведут к потере денег заказчиком продукта. Проблема здесь в том, что вы не доказываете потери заказчика. Ваша логика основывается на том, что мы однозначно идентифицируем указанные вами недостатки, как нечто негативное. А вот то, что эти недостатки приводят к финансовым потерям заказчика — это уже ваша личная теория, не подкреплённая доказательствами.
Мы можем пойти дальше и найти пару примеров того, как гомеопатический препарат предшествовал выздоровлению пациента. Либо, найти пример того, как внедрение языка Go привело к потерям средств заказчика. Всё это будет равноценными логическими ошибками.
Думаю, что если прибегнуть к доказательствам, то выяснится, что для определённых проектов требуются языки с динамической типизацией, для других — со статической. Одним проектам нужны скоростные функции, написанные на Go, исовершенно не требуются объекты с классами. Другим проектам необходима компоновка сущностей в классы и интерфейсы. Это вполне нормально, когда температуру сбивают парацетомолом, а микрофлору кишечника восстанавливают линексом. Было бы странно решать обе задачи мукалтином и говорить, что парацетомол — безрассудная трата денег, если он только сбивает температуру, но совершенно не борется с кашлем. Собственно, все ваши тезисы сводятся к аналогичным выводам, только для ЯП. Нет, я не против доказательного анализа эффективности языков. Более того, такой анализ проводится разработчиками, которые планируют интеграцию нового языка в проект. Допустим, если сервер не справляется с нагрузкой, перевод АПИ с FastAPI (python) на Go может решить проблему, а близость синтаксиса языков покроет риск поиска специалиста по поддержке. Остаётся только оценить возможность нарастить мощность сервера и сравнить с затратами на внедрение языка. Полагаю, что случаи без оценки эффективности внедрения новых языков не являются системными.

Да мне кажется, что проблема в обратном — слишком многие хотят нового только потому, что оно новое. Ну или какие-то не слишком выдающиеся улучшения представляются прорывом.

UFO just landed and posted this here

А что именно "не так" с тем, что люди пробуют новое?
Это ведь и есть развитие, поиск идеала. Пусть не научный, а ремесленный. Но инструменты совершенствуются только благодаря таким энтузиастам, которые рискуют пробовать новое.

UFO just landed and posted this here
что без таких людей мы можем остановится в развитии.

Я не уверен, что согласен с этим тезисом. Ну в том смысле, что вот такие люди и есть те, кто двигают индустрию. По-моему, это не слишком связанные вещи.

Вон тут упомянули язык Го, который мне не слишком эстетически близок, но у него есть чёткий use case — «чтобы даже мартышка смогла». При этом его авторы люди немолодые, знающие и вряд ли склонные к экспериментам ради новизны. Однако же обеспечивают прогресс и новизну тем не менее.
UFO just landed and posted this here
Ну как бы флаг в руки — всегда будут люди, которые пробуют новое ради новизны, это нормально. Вопрос в том, насколько веяния моды должны дёргать остальную массу, которой новизна ради новизны не слишком интересна. В идеале да, хочешь пробовать новую причёску/музыку/блюдо просто ради пробы — пожалуйста, но не надо других втягивать без нужды.

Но без фазы «а давайте попробуем» го бы остался внутри гугла. Что и произошло с многими другими вещами у гугла.


И у гугла, и не у гугла. Чего там, это нормально. Открываем какой-нибудь rosetta code и видим, что он состоит в основном из совершенно экзотических или мёртвых языков.
UFO just landed and posted this here
Ну а какая у человека вообще может быть претензия к тому, что кто-то развлекается приятным ему способом? Кто-то на ютюбе клипы смотрит, кто-то велосипеды изобретает.

А образом Вас втягивают?
Ну так периодически приходится иметь дело с тем, что есть. Вас тоже втягивают. Ну вот самый банальный пример — сказал Эппл, что все теперь пишут на Swift, и вы либо больше не пишете под iOS (меняете профессию), либо пишете на Swift.

Это просто ради примера, у меня нет претензий конкретно к Swift, но да, вот так втягивание и происходит.
UFO just landed and posted this here
Увы и ах, это делает так Эппл, и Фейсбук, и Юнити, и не только. Ну повторюсь, какие у нормального человека могут быть претензии к тому, что кто-то развлекается новым ради развлечения? Да никаких. Проблемы исключительно тогда, когда выкатывается X, и отныне всё только на X, а у тебя библиотека Y, которая с X не работает вообще, и страдай.
UFO just landed and posted this here
но не надо других втягивать без нужды.

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

Я даже согласен с основной идеей — что полезность и новшества следует обосновывать.

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

UFO just landed and posted this here
Современные проблемы с утечкой памяти и недебажным многопоточным кодом — отличные примеры. Проблемы, которые раньше можно было исправить гораздо дешевле.

Частично эти проблемы от стиля кодирования. И утечки памяти, и запутанная многопоточность обходятся правилами и дисциплиной.
Конечно, эти проблемы также можно обойти сборщиком мусора и иммутабельными данными (как в эрланге), или borrow checker'ом (как в расте), или детектором утечек, etc. Но во времена, когда C++ становился популярными, эти штуки работали медленно (или отсутствовали), и на том этапе выиграл С++. Сейчас, спустя N десятков лет, и сборщики не так тормозят, и borrow checker есть. Конечно, это всё сейчас можно вернуть из прошлого, но тогда использовать не удавалось, приходилось соблюдать дисциплину на С++ (что получалось не во всех проектах).


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

Уверен, для каждого "поворота не туда" были причины. Легко уверенно говорить "не туда" спустя десятилетия. Нельзя так уверенно сказать "не туда" для поворотов, которые делаются сейчас.

Почему же нельзя? Реакт, Редакс, реактивщина на стримах, обещания, асинхронные функции и генераторы — всё это повороты не туда.

UFO just landed and posted this here

Мне кажется, если бы человека года так с 2007 по 2021 держали в плену и заставляли писать на Java 1.6 и EJB, то после освобождения и знакомства с современными концепциями и языками программирования, разорванный шаблон должен был бы породить текст как раз вроде этого.


Вроде бы уже прошло достаточно времени (7 лет с релиза восьмерки), чтобы даже самые отъявленные ретрограды поняли, в какую именно сторону развивается Java как язык, и поубавили градус хейта в сторону ФП.

А я вот читал и думал — а может это первоапрельская шутка? Ну так ведь не смешно же… все же хейт, причем хейт весьма странный.
UFO just landed and posted this here

Да примерно такое же, как и ООП, когда там нельзя даже вызывать методы у примитивных типов.


Докопаться можно до всего при желании.

UFO just landed and posted this here

Вы можете использовать JNI, чтобы использовать хвостовую рекурсию и ФП, которое есть в С++. Странен хейт в сторону явы, когда есть JNI, позволяющий писать часть проекта на компилируемом языке, например С++.

Спасибо за ссылку на «Научные основы доказательного программирования».

Ответы на ваши вопросы вполне просты и выводятся из теории рынка: в далёком прошлом ПО писали по-научному; ошибки в программах были, но это именно обычные человеческие опечатки/невнимательность, за каждой программой стояло математическое доказательство её работоспособности. Причины: компьютерное время было в дефиците по сравнению с временем программистов, поэтому запускать ПО с ошибками слишком дорого обходилось. А программистов было слишком мало по сравнению с населением Земли, поэтому порог вхождения был крайне высок (требовалось и знание математики, и машин, и алгоритмов...). Сейчас ситуация обратная: процессорное время слишком дешёво, программистов слишком много, ущерб, который испытывает бизнес от багов в ПО очень низкий, ниже, чем стоимость подготовки команды образованных программистов и их работы. Дешевле запустить 100 000 юнит тестов, чем вычитать код из хотя бы 100 строк и напрячь голову. Таким образом рынок продолжает двигаться и по сей день: программистов становится ещё больше, квалификация их становится ещё ниже, порог вхождения поддерживается на сбалансированном уровне за счёт количества спецификаций, которых надо учить, а ущерб, получаемый корпорациями от багов медленно растёт.

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

Вы серьезно полагаете, что перевод всего кода на си без плюсов уменьшит количество багов?
Почему именно на си, а не ада или что-нибудь с автоматическим доказательством корректности?

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

Что касается систем автоматических доказательств… помнится, теория алгоритмов гласит, что даже проблема остановки не имеет решения. Поэтому они могут найти какие-то ошибки и упростить их исправление, но окончательное заключение, что программа работает, делает то, что заявлено в ТЗ, и ничего более — должен делать человек.

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

UFO just landed and posted this here
Представление систем как конечных автоматов — это одна из возможных стратегий. Не исключено, что в каких-то задачах алгоритм представится таким громадным автоматом, что анализ станет технически невозможным. Продуктивно расмматривать группы состояний по какому-нибудь строгому признакому (фактр-группы состояний), тогда отдельно доказывается корректность перехода от одной фактр-группы к другой и отдельно доказывается корректность переходов внутри каждой фактр-группы. Но вряд ли машина сможет сама провести такую декомпозицию.

Зато в обратную сторону: если есть конечный автомат, то превратить его в код на одном из популярных языков, не должно состоавить проблем.
если есть конечный автомат, то превратить его в код на одном из популярных языков, не должно состоавить проблем.
Не совсем: если отталкиваться от верификаторов, в которых КА (абстрактные автоматы, темпоральная логика, Z-нотация) являются мейнстримной или околомейнстримной штукой, то полноценно оттранслировать их в код достаточно неочевидно, уж слишком они далеки от железа.

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

А они вообще представимы, если на то пошло? КА — это же конструкция без стека, скажем. Оно вообще хотя бы Тьюринг полное?

Обычно КА рассматривается не как вещь в себе, а как часть системы, включающей в себя, например, внешнюю память.


Так, если "добавить" к КА бесконечную ленту — получится машина Тьюринга, которая, вроде как, обладает тьюринг-полнотой.

Если добавить к КА память — то вероятно понадобится адресация, а значит адрес. А там уже адресная арифметика все всеми прелестями. Или же нужно добавить к чистому КА, который представляет собой только состояния, входной символ и переходы между состояниями, скажем переменные, и произвольные «действия», связанные с переходами.

Как-то так обычно и выглядят КА в реальной жизни, а не в фантазиях.

И насколько я понимаю, все вот эти расширения модели КА делают полученный результат нифига не верифицируемым и недоказуемым. Т.е. ничем не лучше любой другой программы, точно так же можно налажать. Разве нет?
Если добавить к КА память — то вероятно понадобится адресация, а значит адрес.
Зачем? В С переменная действительно именованный адрес памяти, но в Хаскеле — именованное значение.
>Зачем?
Затем, что мы про конечные автоматы говорили изначально, а все же не про хаскель. И их расширение путем добавления (динамической) памяти. Я согласен, что моя формулировка «вероятно понадобится адресация» наверное не совсем правильная — адресация понадобится, если мы будем добавлять память «как в C», а это наверное не единственный вариант.

Как вы себе представляете расширение КА памятью в виде именованных значений а-ля хаскель? Я вполне допускаю что такое сделать можно — но во всяком случае сразу не очевидно, как именно.
Можете пояснить, а откуда взялась динамическая память? у нас есть КА с закрытой таблицей переходов, это статика.
Извините, я начал с Глушкова :) Насколько я понимаю, если словарь языка конечен, но язык бесконечен, то полнота по Тьюрингу обеспечивается.
Э… если что — минус не мой, но я честно говоря, тоже такой ответ не понял. Можете более развернуто?

С самого начала — моя мысль была простая — что представить все системы в виде статического КА просто невозможно, потому что такой КА просто на это не способен (даже не вдаваясь в вопрос, будет ли это эффективно или там поддерживаемо). На что мне был дан ответ, что обычно КА это часть более широкой системы, включающей например память. И в этом виде с памятью он типа Тьюринг полный. Ну т.е., Тьюринг полный не статический КА, а некий его расширенный вариант, который в общем-то уже и не КА.

Так что там у Глушкова?
На всякий случай, чтобы убедиться, что я совсем не фигню порю, полез погуглить. Сразу нашел про эквивалентность линейных автоматов с правилом 110 (с доказательством). Да, это не КА, рассмотренные по одиночке, а только вместе с окрестностью, но это вполне себе КА с конечными словарем и таблицей переходов. Память под каждый КА можно выделить единожды, но переменная состояния должна быть мутабельной.
Так что там у Глушкова?
Ну, не очень хорошо, умер даже :) На такой неконкретный вопрос могу только посоветовать глянуть его книгу «Синтез цифровых автоматов», предисловие и оглавление.
Я хоть и практик, но я пожилой практик :) Поэтому кто такой В.М. Глушков — я в общем-то знаю.

Я не очень понимаю, как к КА добавлять память в стиле чистых именованных значений хаскеля. Туда ж писать нельзя, толку с такой памяти?

Да, это я протупил. Даже если сделать что-то типа ping-pong буфера, неявно вылезает указатель.
И насколько я понимаю, все вот эти расширения модели КА делают полученный результат нифига не верифицируемым и недоказуемым. Т.е. ничем не лучше любой другой программы, точно так же можно налажать. Разве нет?

Разумеется, именно потому я и написал что багов меньше не станет.

А, ну да. В этом смысле вообще никаких вопросов. Я просто с первого раза не до конца понял.
Занете одну небольшую программу на С? sudo называется?
Весь мир пользуется. На большей части продакшен серверов есть. Влияние на безопасность всего и вся критическое.
И баг нашелся. Спустя много-много лет использования. Баг влияющий на безопасность.

Может не надо на С?
Наличие некорректных программ, написанных на каком-то языке, не означает, что невозможно писать корректно на нём. Реализация ООП и прочих парадигм на С оказываются изящны и гибки. В конце концов, точно также, как код на С транслируется в ассемблер, код на С++, JAVA, GO и прочих подобных языков может быть транслирован в эквивалентный им код на С. Но если так поступить, увидев результат вы скорее всего захотите выкинуть половину/90%/99.7% получившегося, просто как не выполняющего никакой полезной функции.

В sudo баг нашёлся: переполнение буфера. Сколько раз на всевозможных формумах обсуждалось, как избегать переполнения.
Наличие некорректных программ, написанных на каком-то языке, не означает, что невозможно писать корректно на нём.

Утверждаю, что на чистом C (и на плюсах, в общем-то, тоже, но по немного другим причинам), не обмазанном кучей верификаторов вроде Frama C, невозможно писать корректно хотя б немного сложные вещи.


В конце концов, точно также, как код на С транслируется в ассемблер, код на С++, JAVA, GO и прочих подобных языков может быть транслирован в эквивалентный им код на С.

Даже код на хаскеле может быть транслирован в эквивалентный код на C. У ghc даже одно время была опция -fvia-C (предназначенная в основном для бутстрапинга компилятора на новых платформах, но не суть). Правда, поддерживать результирующий код вы не захотите.


Дело не в том, что C может. C может многое, спору нет. Дело в том, как C может вас ограничить (ну там, как можно наваять что-то на классах и темплейтах на плюсах, или как можно обмазаться монадами в хаскеле для контроля эффектов, или как можно обмазаться зависимыми типами в идрисе для контроля вообще всего). И тут всё плохо: ограничить он вас не может. А я себя хочу ограничивать, потому что внимание у меня так себе, память так себе, концентрация так себе. Я легко могу ошибиться, поэтому я хочу, чтобы язык позволял мне хотя бы контролировать собственные ошибки.


Да и, впрочем, насчёт «может многое»… У меня был один забавный проект, где я писал систему для описания графов обработки данных для HFT, где, в общем-то, каждый такт на счету. Это было много кода на плюсовых темплейтах, где в компилтайме описывался конкретный граф под конкретную стратегию торгов на конкретной бирже, а компилятор минут 10 жевал один TU, раскрывал все эти темплейты, инлайнил функции, и в итоге получал более-менее такой же код, который бы вы могли написать руками под конкретно эту стратегию на этой бирже (ну или выплёвывал сообщение об ошибке на десятки мегабайт, если вы накосячили в графе). В рантайме никакого полиморфизма не было, никаких указателей на функции, ничего лишнего. Более того, в компилтайме были проверки разумности графа (ну там, что у вас потребитель не ожидает событий, которые на такой-то бирже просто не происходят) и всякие оптимизации (например, если к источнику подключён только один потребитель, или все его потребители не меняют данные, то можно эти самые данные не копировать лишний раз).


Как этого добиться на C, кроме как написать на C генератор кода на C?


В sudo баг нашёлся: переполнение буфера. Сколько раз на всевозможных формумах обсуждалось, как избегать переполнения.

Да: использовать языки, где отсутствие переполнения можно доказать. Однако, вы предлагаете использовать C, а не эти языки.

«А я себя хочу ограничивать, потому что внимание у меня так себе, память так себе, концентрация так себе» — существует ли хоть одно направление инженерной деятельности, где не требовались бы сильно развитые перечисленные качества? Получается ли у вас писать хоть на каком-нибудь языке писать с первой попытки без ошибок, пусть даже с помощью всяких утилит?
У меня таже проблема с памятью, концентрацией и т.п, как у автора, а возможно даже хуже. Пробовал хаскел (на что намек в комментарии). Не помогло. Не судьба
Если бы мне поставили задачу, что категорически необходимо взять в команду человека/группу людей, с такими данными… я бы смотрел в сторону ГО. У языка хороший манифест, и если заявленное правда, то в языке мало подводных камней.
Рассуждая дальше, я бы выбрал политику +читаемость -низкоуровневая производительность. Каждый алгоритм стараться вместить в стандартные 25, 40, 80 или сколько строк нынче принято считать за 1 экран. Описание алгоритма должно умещаться в 1 тезис, иначе разбивать его на выполнение цепочки алгоритмов, возможно, с условиями и циклами. Обоснование корректности по стандартному списку из школьного курса: граничные значения, проверка ветвей, циклы, обработка ошибок. Написанный и проверенный код убираем под спойлер, забываем весь только что написанный код, и держим в голове (и на экране) только одну строчку с описанием алгоритма. Когда количество алгоритмов становится больше, то организуем их в структуру. N алгоритмов нижнего уровня убираем под спойлер, а над ними пишем верхний уровень из N/10. Если не получается уместиться хотя бы в N/5, то отбрасываем затею и ищем ошибку в архитектуре.

Таким образом на всём процессе разработки в голове необходимо держать только 1 страницу текущего кода и 1 страницу описаний алгоритмов предыдущего уровня. Это вполне реально. Если рассматривать пирамиду из всего трёх уровней и коэффициентом 5, получается 125 алгоритмов по 40 строк без повторений (5000 строк), среди которых вы сможете свободно ориентироваться не более, чем за 3 обращения к собственной документации. КПД такого кода вряд ли превысит 10%, но даже 1% от теоретического максимума было бы больше, чем мы имеем сейчас в популярном ПО.

Конечно, это всего лишь теоретическое рассуждение, не подкреплённое практикой, но… вдруг.
Обоснование корректности по стандартному списку из школьного курса: граничные значения, проверка ветвей, циклы, обработка ошибок. Написанный и проверенный код убираем под спойлер, забываем весь только что написанный код, и держим в голове (и на экране) только одну строчку с описанием алгоритма.

Куда вы тут засунете хотя бы выражение пред- и пост-условий на выходные и выходные данные? Куда вы напишете, какое глобальное состояние меняет функция?


У языка хороший манифест, и если заявленное правда, то в языке мало подводных камней.

Я тут не так давно смотрел на исходники на go (N недель назад зашла речь о декодерах jpeg'а, и мне стало это интересно сделать на хаскеле, а человек в треде ссылался на код на go как на что-то типа бейзлайна), и, короче, это печально.


Конкретно последнее время я тут игрался с тем, чтобы декодер Хаффмана сделать быстрым, и вот мой код на хаскеле по декодированию:


lookupFast :: Alternative f => HuffmanValueCache -> BitVec -> f (Word8, BitVec)
lookupFast cache bv = case cache `V.unsafeIndex` fromIntegral (takeWord16Bounded cacheBound bv) of
                           (_, -1)  -> empty
                           (w, len) -> pure (w, dropBits len bv)

lookupTree :: Alternative f => HuffmanTree -> BitVec -> f (Word8, BitVec)
lookupTree EmptyLeaf _ = empty
lookupTree (Leaf v) bv = pure (v, bv)
lookupTree (Inner l r) bv | headBit bv == 1 = lookupTree l $ dropBits 1 bv
                          | otherwise       = lookupTree r $ dropBits 1 bv

lookup :: Alternative f => HuffmanDecoder -> BitVec -> f (Word8, BitVec)
lookup HuffmanDecoder { .. } bv = lookupFast valueCache bv <|> lookupTree tree bv

12 строк на всё про всё, из типов всё видно. Вот есть функция:


lookupFast :: Alternative f => HuffmanValueCache -> BitVec -> f (Word8, BitVec)

Она принимает какой-то там кэш, битовый вектор, и возвращает один байт вместе с каким-то другим битовым вектором, который завёрнут в произвольный Alternative f (который, по факту, просто некий моноид на аппликативных функторах, про это позже).
Что по этим типам можно сказать? Хаскель — иммутабельный, поэтому входной BitVec функция точно не модифицирует. Результат завёрнут в Alternative, поэтому эта функция может завершиться неудачей.


Дальше, следующая функция — lookupTree, про неё рассуждения аналогичные.


В итоге можно сделать вывод, глядя на одни только типы этих функций, что их можно спокойно композировать, написав lookupFast valueCache bv <|> lookupTree tree bv — что имеет семантику «попробуй сделать lookupFast, если оно вернуло какое-то неошибочное значение, то верни его как результат всей функции, иначе сделай lookupTree». И мне не надо даже задумываться, например, о том, меняет lookupFast внутри себя состояние битового вектора (например, откусывая некий его начальный кусок) или нет — я этого гарантированно не замечу.


Зачем здесь нужен Alternative? Потому что дальше эта функция используется в двух разных контекстах. В одном месте это обычный привычный Maybe (и тогда empty ~ Nothing, pure ~ Just, а <|> имеет очевидную семантику). В другом месте это уже какой-то хитрый монадный трансформер вида MaybeT (ST s) — то есть, это какая-то локальная мутабельность (которая, тем не менее, не меняет ничего глобального — снова о рассуждениях), которая уровнем выше имеет тот же эффект Maybe. Это, по-моему, топовая реюзабельность кода.


Ну и были бы зависимые типы, можно было бы избежать unsafeIndex (так как cache — вектор длины 2 ^ cacheBound, и takeWord16Bounded cacheBound возвращает не больше cacheBound битиков, поэтому соответствующее значение не может быть больше 2 ^ cacheBound - 1, значит, динамические проверки принадлежности диапазону не нужны).


А, с чего мы там начали, с Go? Как это выглядет на Go? Не знаю, не видел, давайте посмотрим в huffman.go:


func (d *decoder) decodeHuffman(h *huffman) (uint8, error) {
    if h.nCodes == 0 {
        return 0, FormatError("uninitialized Huffman table")
    }

    if d.bits.n < 8 {
        if err := d.ensureNBits(8); err != nil {
            if err != errMissingFF00 && err != errShortHuffmanData {
                return 0, err
            }
            // There are no more bytes of data in this segment, but we may still
            // be able to read the next symbol out of the previously read bits.
            // First, undo the readByte that the ensureNBits call made.
            if d.bytes.nUnreadable != 0 {
                d.unreadByteStuffedByte()
            }
            goto slowPath
        }
    }
    if v := h.lut[(d.bits.a>>uint32(d.bits.n-lutSize))&0xff]; v != 0 {
        n := (v & 0xff) - 1
        d.bits.n -= int32(n)
        d.bits.m >>= n
        return uint8(v >> 8), nil
    }

slowPath:
    for i, code := 0, int32(0); i < maxCodeLength; i++ {
        if d.bits.n == 0 {
            if err := d.ensureNBits(1); err != nil {
                return 0, err
            }
        }
        if d.bits.a&d.bits.m != 0 {
            code |= 1
        }
        d.bits.n--
        d.bits.m >>= 1
        if code <= h.maxCodes[i] {
            return h.vals[h.valsIndices[i]+code-h.minCodes[i]], nil
        }
        code <<= 1
    }
    return 0, FormatError("bad Huffman code")
}

44 строки, которые лично мне читать куда сложнее (и магические числа типа 8 очень забавные), и про который рассуждать сложнее (меняется состояние декодера? не меняется? хрен знает, читай тело функции). Кстати, ещё забавно, что они тоже используют этот финт с быстрой лукап-таблицей для кодов покороче, но вот почему-то слили обе функции в одну. Учитывая, что это официальная библиотека go, а я — так, от скуки этим занимаюсь, когда задалбывает доказывать вещи про другой код, и хочется не забыть хаскель — получается забавно.

Вот еще бы краткое введение с объяснением смысла алгоритма и можно писать отдельную статью.

Пробовал хаскел (на что намек в комментарии).

Там на самом деле намёк на Idris, Agda и прочие языки с завтипами.


Хотя если считать аварийное завершение приложения хорошим выходом, то подойдёт любой современный язык кроме Си и С++ (причём последний "виноват" лишь в совместимости с Си).

Вы путаете наличие каких-то качеств и их безошибочную работу

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

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

Вы говорите про ошибки, которые расположены рядом, пускай и на соседних строка, когда глядя на них очевидно, что эти строки ошибочны. Например, если конкатенировать переменные с sql запросом, то очевидно, что они должны быть экранированы, и для этого хватит просмотреть условно пять строчек вверх. В то же время, в си ошибки не очевидны, и проблема заключается не в одной очевидной строке(вызов запроса с не экранированными переменными, а как минимум в двух не очевидно выглядящих. Например, переполнение буфера это две строки — выделение памяти и запись в этот буфер. Обе строки выглядят правильными сами по себе, так как это среднестатистический код на си, но в сочетании дают ошибку. Более того, даже если обе эти строки и верны, а потом, по каким-то причинам между ними мы вставили третью строку, которая либо перевыделяет память, либо меняет значение указателя, то только сочетание этих двух строк приводит к ошибке. Тут будет недостаточно просмотреть код на пять строк вверх, так как запись в буфер может быть в одной функции, создание буфера в другой, а вызов всего этого — в третьей, и соответственно нужно будет просмотреть сразу три функции, на одно простое действие. Никакой внимательностью или аккуратностью этого не достичь, так как это требует слишком большого объёма работы.
В sudo баг нашёлся: переполнение буфера. Сколько раз на всевозможных формумах обсуждалось, как избегать переполнения.
Нужно всего лишь сделать пару тысяч прыжков по коду и прочитать в каждом из них условно от одной, до нескольких сотен строк, ради одной маленькой правки, да.
А си — на нём получается компактный код, и сам стандарт компактный. Что сильно облегчает доказательство.
Размер стандарта влияет лишь на сложность реализации компилятора, и возможно библиотек времени исполнения. После того, как это написано, размер стандарта явно ни на что не влияет, вы же не пишете свой компилятор? Например, если добавить в си указатели с размером, то это усложнит стандарт, но упростит доказательство. Про компактность странно слышать — попробуйте, например на си написать аналог
new DateTime().format("Y")
и вам потребуется сразу три строки, вместо однй, две из которых будут связаны с управлением памятью. Если конкатенировать результат, то вам потребуется ещё как минимум три строки, в то время как в других языках это будет по прежнему хватит одной.
В конце концов, точно также, как код на С транслируется в ассемблер, код на С++, JAVA, GO и прочих подобных языков может быть транслирован в эквивалентный им код на С. Но если так поступить, увидев результат вы скорее всего захотите выкинуть половину/90%/99.7% получившегося, просто как не выполняющего никакой полезной функции.
Любая достаточно сложная программа на C или Фортране содержит заново написанную, неспецифицированную, глючную и медленную реализацию половины языка Common Lisp
Это имеет смысл разве что на hello wold, на более сложных задачах вам всё равно придётся писать этот код самостоятельно. Так например, некоторые программы до сих пор не любят пути с юникодом и пробелами, так как программисты на си, вместо того, чтобы использовать библиотечные строки, которые нормально работают с такими путями, изобретают велосипед самостоятельно, и каждый самостоятельно исправляет в нём баги. И касается это не только строк, но и кучи других проблем.

Я с вами в общем согласен, но есть пара уточнений:


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

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


Размер стандарта влияет лишь на сложность реализации компилятора, и возможно библиотек времени исполнения. После того, как это написано, размер стандарта явно ни на что не влияет, вы же не пишете свой компилятор?

Как человек, долго писавший на плюсах и ушедший с них потому, что стандарт плюсов принципиально непознаваем — это важно и для пользователя языка, если вы хотите писать код, соответствующий стандарту. Например, ни один компилятор не ругнётся на создание (не разыменование, а именно создание) указателя на более-чем-на-один-элемент-дальше-от-конца-массива, тогда как это UB. Или, например, класс, предоставляющий тривиальный тип в mmap-backed-памяти — строк 15, если делать прямо (и неправильно), полтора дня чтения стандарта, если делать правильно.

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

Нет, не возможно и не должна.

Почему не должна? Как вы будете проверять, что строку не заэкранировал кто-то уровнем выше?


Иными словами, у вас должен быть ровно один уровень в вашей цепочке абстракций, где экранируется ввод, и вся работа с БД должна идти через него. Взгляд на N строк выше может помочь убедиться, что этот уровень работает корректно, но не поможет убедиться, что он один, и что всё идёт через него.

Так работают соглашения. По умолчанию предполагается, что никакая строка уровнем выше не экранирована. Если же такое экранирование находится — это ошибка, и она исправляется. И это, как правило, работает.


Для большей же надёжности можно для экранированных строк отдельный тип данных ввести.

Для большей же надёжности можно для экранированных строк отдельный тип данных ввести.

Да, именно. Я к этому и веду, но на C это делать больно.

Не больнее работы со строками...

— new DateTime().format(«Y»), — отличный пример. В том же си, когда printf парсит строку, чтобы вывести обычное число, практика была признана порочной; в с++, к примеру, механизм заменили. На другие языки программирования это не распространяется?

Вы написали код на 2 строчке короче, чем в си, но какой ценой: создание объекта (наверняка со всякими защитами и кучей служебных полей), использование неявного свойства, что конструктор без параметров инициализирует текущей датой и временем, использование загадочного формата «Y», который не соответствует ISO 8601, в памяти зависает объект на неопределённый срок.

Дял ответа на ваш вопрос следует впомнить, что язык программирования — это просто инструмент, и создавался с определённым намерением. Если намерение человека не согласуется с таковым создателя, значит инструмент ему не подходит. Поэтому вопрос можно свести к тому «с каким намерением создавался тот или иной язык».
Проведя аналогичное размышление (https://habr.com/ru/company/timeweb/blog/551224/) можно сделать вывод, что «целевой» язык должен иметь возможность расширять свой функционал без превращения в новый язык. Логично предположить, что у такого языка должна быть какая-то базовая комплектация. Достаточно минималистичная. Но подождите, такой язык уже существует! Это си. Простенький синтаксис, стандартная библиотека на уровне «минималный набор для выживания» и возможность создавать неограниченное количество собственных библиотек.

Давайте тогда подключим какой-нибудь модуль с уже определённым объектом now, и напишем now->year. Полученное значение, разумеется, будет int. sprintf(buf,"%4d",now->year), я считаю, что писать так — я считаю неуважение к самому себе (и пользователям программы), поэтому подключим ещё парочку модулей и сделаем:
/* Предварительно запишем что-нибудь */
WriteC(stream_object,«Сегодня „);
/* Припишем текущий год в одну строчку */
WriteI(stream_object,now->year);
/* Ещё запишем что-нибудь */
WriteC(stream_object,“ год.»);
Согласен, что на с++ есть дополнительная изящность: stream_object<<«Сегодня „<<(now->getyear())<<“ год»;
но смысл не меняетя.

наверняка со всякими защитами
И что в них плохого? У си практически для каждой вещи есть какое-то примечание и если человек его не прочитал, то он обретёт проблему на ровном месте. Если это распространённый проект, то скорее всего, что большинство таких ошибок уже найдены и с ними столкнуться не придётся, но ещё не факт. А непопулярные проекты — вообще кладезь подобных ошибок. Некоторые примечания не пишутся в явном виде, как например инъекция:
На другие языки программирования это не распространяется?
В других языка можно увидеть память если не посмотреть в документацию?
но какой ценой: создание объекта (наверняка со всякими защитами и кучей служебных полей), использование неявного свойства, что конструктор без параметров инициализирует текущей датой и временем, использование загадочного формата «Y», который не соответствует ISO 8601, в памяти зависает объект на неопределённый срок.

Вы уверены, что во всех ста процентах случаев можно избежать выделения памяти в куче, например если это будет регулярное выражение или какой-то ещё более сложный объект и его нужно будет возвращать из функции? В примере ниже вы тоже создаёте now, разве это не объект? Вы полагаете, что неопределённый срок возможен только со сборщиком мусора, но на практике, это может возникать и при ручном управлении памятью. В гноме тоже есть(или уже поправили?) утечка памяти, которая кочевала из релиза в релиз, но до неё никому не было дела, все только переводили стрелки.
я считаю неуважение к самому себе (и пользователям программы), поэтому подключим ещё парочку модулей и сделаем:
Скорее всего, вам придётся ограничиться своим кодом, а при интеграции с библиотекой, придётся использовать более низкоуровневый код.
WriteC(stream_object,«Сегодня „);
Этот код очень зависим от контекста, и если потребуется запись в строку, например для последующей обработки, отправки в базу данных, то потребуется создавать отдельный буфер, что добавит ещё строки три(создание, получение си строки, освобождение). Возможно, эту строку нужно будет ещё раз скопировать уже в массив, либо придётся возвращать сразу уже буфер, что приведёт к проблемам так как другие функции принимают char*, а не StringBuilder*, либо деструктор буфера не должен освобождать строку…

Если в каких-то других языках для решения проблемы можно подключить какой-то пакет, то в си практически каждая проблема приведёт к необходимости написания шаблонного кода.
Проведя аналогичное размышление (https://habr.com/ru/company/timeweb/blog/551224/) можно сделать вывод, что «целевой» язык должен иметь возможность расширять свой функционал без превращения в новый язык.
И в чём это заключается применительно к си? Это лисп можно расширить, за счёт его развитой системы макросов, или руби, за счёт его динамичности буквально во всём, когда прямо в коде класса можно вызвать код, который, например определит какой-то метод.
UFO just landed and posted this here

Не совсем. Задача — доказать тезис «папа C может всё что угодно». Если мы рассуждаем о C как о языке программирования, а не о промежуточном представлении (в смысле LLVM IR, скажем), то кодогенераторы на других языках — это читерство какое-то.

UFO just landed and posted this here

Так исторически сложилось. Когда люди начали писать этот фреймворк N лет назад, они не представляли, как расширится спектр задач, и во что превратится этот фреймворк. Но да, при мне и с моим участием начались разговоры о том, чтобы выкинуть это всё и запилить свой собственный DSL (ой, правда, тут автор меня опять не будет одобрять, ну да ладно).

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

Ну а ежели такового нет, то я выбираю те языки код на которых компактный, предсказуемый и легко проверяемый. Вы можете что то предложить?
Вы про баги в спецификации языка, в его интерпретаторе/компиляторе или баги соответствия ТЗ и реализованной программы?
Баги любого типа, ибо пользователям в общем то все равно почему у них все упало.

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


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

Парадигмы высокоуровневых языков огриничивают возможности программиста.

Возможности по стрельбе в ногу? Так в этом и смысл.

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

И возможно это вкусовщина, но возможность выстрелить себе в ногу весьма дисциплинирует. Ибо довольно странно видеть защищённый от стрельбы в ногу «Hello world» размером десяток мегабайт и исполняющийся секунды занимая при этом все 8 ядер.
Но, раз уж мы затронули эту тему, пару слов про ООП-диссидентов. Сложно сказать, что движет этими несчастными.

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

Так ведь и для ООП и Java этого никто не делал.
Вы потом правда пишете, что
Реальность такова, что доказательство эффективности различных химических веществ — мероприятие крайне недешевое и доступно только Большой Фарме. А те не будут тратить деньги на лекарства, на которых они не смогут сорвать куш.

Ну так у IT и денег больше, чем у фармкомпаний и тестировать не так много языков нужно.
Эффективность парацетамола не доказана, и никто не знает, почему он делает то, что делает. Но помогает же!

Ну это откровенного говоря не правда. Испытали его давно. И не раз. И даже с необычной стороны.
Доказательная медицина предполагает определенный алгоритм испытаний. Главным из них является т.н. «двойное (или тройное) слепое рандомизированное исследование» с участием десятков тысяч добровольцев. Говорят, что это реально очень дорого. В отношении парацетамола такие исследования, на сколько мне известно, не проводились.

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

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

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

А где, собственно, про доказательства? Эту бы обличительную энергию, да в нужное русло: в обзор современных методов и инструментов формальной верификации, использования зависимых типов, статического анализа…

Но все меняется в тот момент, когда программа начинает делать что-то не то. Не то, что от нее ожидали. Возникает закономерное желание понять: почему она поступила именно так? И вот здесь выясняется, что любой императивный язык буквально "ткнет пальцем" в место в коде, где проявилась проблема и откуда ее можно локализовать.

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


С декларативными языками иначе.

Хаскель — не декларативный язык. Но это так, мелочи.


Скажем так, когда я писал код на плюсах с 15-18 годами опыта за плечами, и когда писал на хаскеле с пятью годами опыта — во втором случае ошибок было сильно меньше, а отладчик (или, тем более, тяжёлая артиллерия вроде asan и тому подобного) нужен примерно никогда. Ну, да, нет в хаскеле стека (но не потому, что он функциональный, а потому, что он ленивый и по факту вычисляет граф, в идрисе вот стек есть и читается даже gdb). Ничего страшного. Условно, после того, как я делаю рефакторинг, и тайпчекер доволен, мои тесты тоже сразу зелёные.


Что там проблематично — о производительности рассуждать чуть сложнее, но это совсем другая история.

Что там проблематично — о производительности рассуждать чуть сложнее, но это совсем другая история.

Насколько удобно там работать с изменяемым состоянием? Условно, есть два массива и в цикле мы вызываем
a[i].e -= 5;
b[i].h -= 10;
Теперь мы захотели изменять ещё и соседние элементы при каком-то условии.
a[i].e -= 5;
b[i].h -= 10;
if(c) {
	b[i - 1].h -= 10;
	b[i + 1].h -= 10;
}
Насколько сильно изменится код, при том, что условие может зависеть от предыдущей итерации цикла?
Насколько удобно там работать с изменяемым состоянием?

Заведомо не хуже, чем на ваших любимых императивных языках :]


Любую подобную императивщину и мутабельность можно запихнуть в монаду ST, например (и иметь её доказанно изолированно, чтобы она не утекла за границы чётко очерченного загончика). Но даже это не обязательно: иногда достаточно иммутабельной (но выглядящей почти как мутабельная) монады State, например.


Теперь, конкретно отвечая на ваш вопрос — цикл будет записан как рекурсивная функция. Любое состояние цикла будет выражаться как ещё один аргумент этой функции. Вот, например, так выглядит относительно недавно написанный мной кусок, декодирующий набор ac-коэффициентов в пожатом jpeg'е:


decodeACs :: HuffmanDecoder -> BitVec -> Maybe (V.Vector Word16, BitVec)
decodeACs hm bv₀ = runST $ runMaybeT $ do
  mvec <- VM.replicate blockLen 0
  let go cnt bv | cnt >= blockLen = pure bv
                | otherwise = do
        (rawCode, bv') <- first fromIntegral <$> lookup hm bv
        case rawCode of
             0    -> pure bv'
             0xf0 -> go (cnt + 16) bv'
             _    -> do let (skip, code) = (rawCode .>>. 4, rawCode .&. 0x0f)
                            (bits, bv'') = (takeWord16Bounded code bv', dropBits code bv')
                            value = decodeWord8 code bits
                        VM.unsafeWrite mvec (cnt + skip) value
                        go (cnt + skip + 1) bv''
  bv <- go 0 bv₀
  (, bv) <$> V.unsafeFreeze mvec
  where blockLen = 63

Можно это ещё дополнительно в монаду State завернуть, чтобы не таскать явно с собой BitVec (и избежать всех этих уродливых bv' и bv''), но тогда оптимизатору приходится делать больше работы, и сборка для профилирования по производительности ещё более отличается от «релизной» сборки, что ещё более затрудняет отладку проблем с производительностью.


Ну и в очередной раз подчеркну, что были бы линейные типы — не нужен бы был unsafe в unsafeFreeze, были бы зависимые типы — не нужен бы был unsafe в unsafeWrite. Вернее, линейные типы уже как раз добавили в недавнем релизе компилятора, поэтому теперь осталось дождаться, когда поддержка появится в библиотеках.

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

Посмотрел на примеры State
import Control.Monad.State
main = do 
	r <- runStateT (do
		modify (+1)
		(_, t) <- runStateT (do
			modify (+1)
			c <- get
			lift $ lift $ print c
			) 1
		modify (*t)
		s <- get
		lift $ print s 
		modify (+3)
		) 5
	print r

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

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

С доказательной медициной все проще. Как правило, проблема одна и четко формализирована. Нужно доказать что средство относительно безопасно и с определенной степенью эффективности решает проблему. С программированием это будет работать когда мы имеем четкие требования к приложению и они неизменны (детальное ТЗ, готовые макеты на все случаи жизни). В этом случае мы действительно можем выбрать максимально эффективный язык методом доказательного программирования. Но даже приложения в одной группе зачастую имеют сильно различные требования, проводить такой эксперимент на каждое будет слишком затратно.

Детальное ТЗ требует формальной спецификации. Если есть такая спецификация, то есть и язык на котором она написана. Программная инженерия это и есть написание спецификации ТЗ и написание транслятора языка спецификации в исполнимый объект. Перевод спецификации ТЗ в промежуточный код на ЯП высокого уровня, будь то Си или LLVM IR, а так же перевод из промежуточного представления в исполнимый код задачи полностью формальные, а значит реализуемы без участия человека. Создание самого же языка спецификаций ТЗ, скорее, научная работа.
Если у вас есть детальное ТЗ и/или формальная спецификация, то 90% вашей задачи уже решено. Весь software engineering выстроен на проблеме того, что иметь такую спецификацию — утопия.
Честно говоря, не совсем понял в чём посыл статьи. Да, программисты играют в войнушку с кривыми палками, и никого это особо не волнует. Программирование все-таки больше про деньги, и если вдруг решат бороться за эффективность и/или производительность труда в рамках компании, выбор языка будет далеко не на первом месте.

Не вполне понимаю, зачем в данной ситуации писать, как всё плохо. Имхо, не плохо. Обычное развитие: новая идея кажется хорошей, её все используют, потом появляется идея получше, и все плюются на предыдущую, как плевались на пред-предыдущую. В чём смысл плакать о величине свалки идей?


Некорректно приравнивать программирование к медицине, авиации и прочим областям, где есть риск для жизней людей. "Умерший" проект можно переписать. Умершего человека — не вернуть. Отсюда и суровые требования к медицине, отсюда и отсутствие суровых требований к разработке. Они не нужны, только замедлят прогресс там, где будут введены. Введёте их — и отстанете от других. Только, пожалуйста, не пропихивайте этот бред на законодательном уровне, рекомендую сначала на себе опробовать (доказательность!).

UFO just landed and posted this here

Наверно, это проблемы авиации, что в ней используется "software provided as-is". Нужно что-то более надёжное — переписывайте. Глупо предъявлять к системе подсчёта лайков требования как к самолёту — это неэффективно, высокая надёжность не везде необходима.

UFO just landed and posted this here
Я говорю что индустрия виновата в том, что не предоставляет однозначную информацию о надежности.

Разве "as-is" — не исчерпывающая информация о "надёжности"?

UFO just landed and posted this here
Интересный пост. В чём-то автор прав, в чём-то нет, но определённо излил душу, рассказал о наболевшем.

Считаю, что обсуждать пост не надо, он самодостаточен и тем прекрасен :-D

А где абзац про ООП и джаву то? Как к ним относится доказательная медицина? Если продолжать аналогии — если джава лечит все болезни, то зачем вообще эта «альтернативная медицина»?

Зачем усложнять, когда всё просто?!
«Выживают» те ЯП, которые приносят большую прибыль.

Так что популярные ЯП уже доказали свою «правильность».

Грубо говоря «доказательство» происходит «здесь и сейчас» и «напрягаться» не надо «от слова совсем». ;-)
> За его запятой может прятаться вселенная, а за вашим «плюсиком» ну максимум — конкатенация строк.
$result=universe($parameters);
$universe=new Universe($parameters);

Длинно и токсично. Доказательная медицина натянута как сова на глобус. Назвать Java ООП, императивный код лёгким, Haskell декларативным, а Go с его проверками легко читаемым — знатный троллинг.


Все эти новые модные тенденции и трюки появляются как раз с целью сделать разработку дешевле и быстрее. Каждая технология заимствует хорошие идеи из других технологий. Там где есть юзер интерфейс развивается ООП(swift, kotlin, ts). Java back всё больше похож на fp благодаря oracle и spring с анемичной моделью. Проверить это утверждение об убелевении — не проверишь.

UFO just landed and posted this here

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


Продираться через блевотину в поисках хотя бы вопросов, а не то, что ответов… так себе.


Я считаю, что подобным материалам не место на хабре.

Спасибо за ценный отзыв, Николай Михайлович!
Only those users with full accounts are able to leave comments. Log in, please.