LINUX.ORG.RU

Муки выбора языка программирования

 , , , ,


2

4

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

Хочется, чтобы у языка были:

  • библиотека для загрузки/выгрузки изображений с поддержкой широкого круга форматов
  • биндинги для sdl2
  • работа с битовыми массивами размером больше чем 64 элемента (с поиском единиц)
  • перегрузка оператора индекса в том числе при присвоении
  • ассоциативные массивы с лаконичным доступом к элементам
  • документацией с поддержкой мобильного просмотра в 2023 году-то
  • поддержкой компиляции для мобильных архитектур
  • нормальный полиморфизм, а не как в Rust
  • востребованность на рынке труда

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

C++ и Rust имеют очень странные конструкторы для битовых массивов. Может это проблема документации, но я с ходу не нашёл как мне создать битовый массив из готового байтового массива, чтобы каждый байт превратился в 8 бит.

Haskell имеет поддержку даже многомерных битовых массивов, но вот документацию на мобильном листать не удобно. В принципе не критично, но я не уверен что haskell вообще подходящий инструмент для моей задачи. А задачу мою можно найти по тегу «гексагональный пиксель» здесь.

Что выбрать?

★★★★★
Ответ на: комментарий от monk

В разборе C++ версии замечательная фраза «Как мы уже увидели, даже маленькие исправления могут быть абсолютно непредсказуемы в зависимости от компилятора».

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

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

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

Или не будет.

fluorite ★★★★★
()
Ответ на: комментарий от unsigned

Неустранимо. Тогда из C++ получится Java.

Дело в том, что основной UB — это выход за границу массива или доступ к освобождённой памяти. То есть для устранения UB необходимо автоматическое управление памятью. Максимум, что можно сделать — попытаться отследить типовые ошибки, а то, что отследить невозможно, пометить как unsafe. Так родился Rust.

Но это спасает только для достаточно простых конструкций. Количество unsafe в программах на Rust очень велико. Хотя это всё равно шаг вперёд: втроём и по семь раз надо просматривать не весь код, а на порядок меньше.

monk ★★★★★
()
Ответ на: комментарий от monk

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

monk ★★★★★
()
Ответ на: комментарий от monk

Насколько я понял пропозал, идея в том чтобы ограничить компиляторщиков. Чтобы UB не приводило к непредсказуемому переписыванию логики и было локальным. Хотя что тут можно сделать с порчей памяти - хз. А для массивов таки предлагают обязательную проверку границ.

Что-то в духе Rust, да. Cpp2 уже в этом направлении идет. А куда деваться, если оно на пятки наступает.

unsigned ★★★★
()
Ответ на: комментарий от unsigned

А для массивов таки предлагают обязательную проверку границ.

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

Что-то в духе Rust, да. Cpp2 уже в этом направлении идет. А куда деваться, если оно на пятки наступает.

У Rust проблема в том, что приходится очень много синтаксиса вписывать ради отслеживания времени жизни. А C++ и так ужасно перетяжелённый. Поэтому делать надмножество С++ с возможностями Rust вряд ли осмысленно.

monk ★★★★★
()
Ответ на: комментарий от monk

в Rust приходится в unsafe заворачивать все обращения к элементам массива

Так в случайном порядке элементы массивы редко нужны, а если по порядку, то итераторы, говорят, решают.

unDEFER ★★★★★
() автор топика
Ответ на: комментарий от monk

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

WatchCat ★★★★★
()
Ответ на: комментарий от monk

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

Ерунда полнейшая, вот ошибки с памятью прекрасно ловятся санитайзером. И количество таких ошибок много меньше, чем вы здесь преподносите, гораздо вероятнее (во много много раз) сделать логическую в каком-нибудь алгоритме, в любом языке, это очевидно любому, кто писал что-то сложнее printf("hello world);

Но это спасает только для достаточно простых конструкций. Количество unsafe в программах на Rust очень велико. Хотя это всё равно шаг вперёд: втроём и по семь раз надо просматривать не весь код, а на порядок меньше.

Ерунда это, а не шаг вперед. Rust завёз себе вагон санитайзеров по примеру плюсов и обмазался ими по самые уши, ловить ошибки там нужно таким же способом - тесты, санитайзеры, а не чтением unsafe перед сном. А зачем тогда нужна вся эта ржавая дрочка вприсядку, которая доставляет уйму боли на ровном месте - загадка.

kvpfs ★★
()
Ответ на: комментарий от kvpfs

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

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

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

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

monk ★★★★★
()
Ответ на: комментарий от monk

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

unC0Rr ★★★★★
()
Ответ на: комментарий от monk

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

Надо писать тесты, и на них гонять с санитарами. Я ещё раз заостряю внимание на том, что в коде могут остаться логические ошибки и при некоторых входных данных можно получить белиберду на выходе, и такая вероятность значительно выше пропустить UB, которым вы здесь пугаете. Если человек совсем параноик и пишет для «АЭС», то может делать свои безопасные структуры данных и проверять выход за границы, объявить MUL вместо умножения и грепать «*» в проекте. Большенству это не надо.

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

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

kvpfs ★★
()

Я не знаю. Здесь дискуссия пошла в сторону неопределённого поведения и вопросов насколько это опасно, но…

Поговорил с другом который в своё время и на Haskell много написал, а сейчас Rust изучает и уже нащупал его слабые места.

Так вот, он говорит что на Rust есть такое волшебное ощущение, что если код компилируется, то он сразу работает. Подобное же он испытывал на Haskell.

Не то чтобы на D я так много спотыкался на ошибки runtime’а, но когда программа долго выполняется, а потом вылетает из-за «orphan %s» в только что расставленным дебаге, то это вымораживает. Если в Rust хотя бы эта проблема решена, то это уже большое подспорье.

unDEFER ★★★★★
() автор топика
Ответ на: комментарий от kvpfs

Надо писать тесты, и на них гонять с санитарами.

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

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

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

Если человек совсем параноик и пишет для «АЭС»

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

monk ★★★★★
()
Ответ на: комментарий от monk

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

kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 1)
Ответ на: комментарий от kvpfs

Так я и пишу. Если скорость настолько важна, что с UB можно смириться, то C++ однозначный выбор.

Просто, когда у тебя в проекте на сотню тысяч строк внезапно начинает происходить что-то странное, то перебирать всю сотню тысяч в поисках UB занятие не из приятных.

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

monk ★★★★★
()
Ответ на: комментарий от monk

Тупость, конечно, такой бред нужно вычищать, почему это пустой бесконечный while - UB, и почему clang решает сгенерить такой код (даже хоть это ЮБ формально)? Совершенно понятно что программист ожидает от вечного цикла. ГЦЦ ведёт себя адекватно, а не пытается назло нагадить. Я стал хуже думать о шланге.

kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 1)
Ответ на: комментарий от kvpfs

почему это пустой бесконечный while - UB

Потому что так написано в Стандарте.

Совершенно понятно что программист ожидает от вечного цикла.

Вечный цикл без побочных эффектов в Си++ недопустим.

monk ★★★★★
()
Ответ на: комментарий от monk

Нет, UB - это значит в стандарте ничего не написано, всё отданно на откуп компиляторописателям, они вольны делать что угодно. Но шланг решает специально нагадить, он мог хотябы выйти из цикла, но с какой такой радости он проваливается в другую функцию? Потому что может и решил показать «кто здесь папа»? Ну ок, мне такой компиль не нужен. Это как UB неактивное поле union, но фактически все компиляторы поддерживают, а msvc даже на strict aliasing кладет и делает валидным лютое UB.

kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 1)
Ответ на: комментарий от kvpfs

Нет, UB - это значит в стандарте ничего не написано, всё отданно на откуп компиляторописателям, они вольны делать что угодно.

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

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

Оптимизирует. Если этот код всегда UB, то всё тело main можно выкинуть.

Это как UB неактивное поле union, но фактически все компиляторы поддерживают, а msvc даже на strict aliasing кладет и делает валидным лютое UB.

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

monk ★★★★★
()
static UNIT: &'static &'static () = &&();

fn foo<'a, 'b, T>(_: &'a &'b (), v: &'b T) -> &'a T { v }

fn bad<'a, T>(x: &'a T) -> &'static T {
    let f: fn(_, &'a T) -> &'static T = foo;
    f(UNIT, x)
}

удачи всем растоманам коммитить в продакшн без санитайзеров :))

Lrrr ★★★★★
()
Ответ на: комментарий от monk

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

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

undefined behavior
behavior for which this document imposes no requirements
[Note 1: Undefined behavior may be expected when this document omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message)

Оптимизирует. Если этот код всегда UB, то всё тело main можно выкинуть.

Какая там оптимизация? Я первый раз в жизни такое вижу, это редкость, ничего на ней не выйграть. Шланг лишь роняет свою репутацию такими фокусами.

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

Ну с union’ами вряд ли, а strict aliasing’ом тот же ГЦЦ внимательно смотрит когда оптимизировать надо, а когда нет, ещё нужно умудриться сгенерить невалидный код. Я не призываю писать на ЮБ, лишь показываю, что вы преувеличиваете проблему.

kvpfs ★★
()
Ответ на: комментарий от monk

Кстати, если говорить про Rust, так это вообще сплошное UB, у него ведь вообще нет стандарта, лишь реализация. Захотят и в следующей версии что-нибудь отломают, тормозов вообще нет вроде плюсового стандарта. И жалуйся потом в 123 East_очень_далек_стрит, CA 94041 USA

kvpfs ★★
()
Ответ на: комментарий от kvpfs

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

Может гарантировать, но не обязана. UB для того, чтобы компилятор мог при оптимизации считать, что его никогда не будет. Например, x+1>x всегда истинно. Или x*2/2 можно заменить на x.

Тот же union и aliasing позволяет операции производить в регистрах, а не писать на каждом шаге в память.

monk ★★★★★
()
Ответ на: комментарий от monk

Это ваши фантазии. Стандарт - это надстройка над реализацией. Естественно, что стандарт не может описать всё, хотя бы из-за множества платформ, вот что не попадает под его взор и обзывается unspecified и undefined. Реализация вольна делать с этим что угодно - бить по рукам и тварить лютую дичь (шланг) или какое-то адекватное поведение (ГЦЦ). Благо, что в случае плюсов есть выбор. Не было бы у плюсов стандарта, не было бы и никакого UB, все дружно хрюкали, что плюсы без этой гадости.

kvpfs ★★
()
Ответ на: комментарий от kvpfs

Не было бы у плюсов стандарта, не было бы и никакого UB, все дружно хрюкали, что плюсы без этой гадости.

А то, что один и тот же код при разных уровнях оптимизации работает по-разному, тоже бы никто не заметил?

annulen ★★★★★
()
Ответ на: комментарий от monk

aliasing позволяет операции производить в регистрах, а не писать на каждом шаге в память.

Ну вот посмотрите как непросто заставить ГЦЦ сгенерить невалидное, если он остеживает указатели и не потерял весь путь, то всё будет работать норм. Его нужно специально запутать и передать пару «разных указателей на одно» в функцию в другом модуле.

kvpfs ★★
()