Как стать автором
Обновить

Комментарии 89

Интересно, для начала, узнать размер оверхеда от проверки всех массивов на выход за границы.
Сахарок с auto, бесспорно, хорош, как и foreach.
Про TLS не очень понятно, ну и extern C тоже.

По умолчанию все проверки убираются из релизной версии кода, кроме функций, помеченных атрибутом @safe. Но также есть опция компилятора -boundscheck, которая позволяет настроить желаемое поведение.

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

Но она не дармовая.

Для однопоточного приложения никакой разницы.

Для однопоточного оно и не надо
А чем данный подход лучше C-расширений для Python?
Другой синтаксический сахар?
В Питоне — интерпретатор и сборка мусора.
Тут — компилятор и нет сборки мусора.
Это я понимаю. Просто какая выгода использовать не язык Си?
Делать embedded на D вряд ли кто-то станет.
Для быстрых параллельных вычислений есть кресты.
А для прикладных задач работы с данными — очевидный выбор Python.
Зачем тогда нужен этот betterC? В чем поинт?

В том, что всё это можно делать на одном языке.

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

— — — далее уже не уровень BetterC / C -----------------------
Для быстрых вычислений — чаще всего обязательна многопоточность (применение SIMD считаем равносложным для языков). Здесь сравнивать стоит удобство абстракций параллельного вычисления. Здесь нужен уже полноценный D с его библиотекой параллелизации и упрощения работы при многопоточности

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

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

Есть в теме спецы по C++? Я со стороны вижу, что 17 и 20й стандарты сильно поменяли(меняют) язык.
Но как это изнутри чувствуется? Есть ли потребность в замене c++, если используется наиболее современный стандарт?

C++ 20 никак не чувствуется и ещё год, как не почувствуется.

На проде — это понятно.
Но всё же.

Да там и без «проды». Компиляторы вроде все ещё в полном объёме стандарт не поддерживают.
С каждым стандартом всё лучше и лучше. Вспоминая С++ без лямбд, std::move и прочих плюшек, это, мягко скажем, раздражало.
С другой стороны, порог входа повышается, новичку уже сложнее разбираться в коде со всеми новомодными прибамбасами. А впереди ещё пугают концептами.

концепты сами собой напрашивались, но синтаксис как обычно сложный.

Эта тема предлагается как замена С. ИМХО, вполне адекватная — легкий путь.

Но D уже не позиционируется как замена С++, слишком разные парадигмы в библиотеках. И да, вряд ли кто на такое пойдет. Скорее, как кооперация
НЛО прилетело и опубликовало эту надпись здесь

C введением 17 циклы по kv-хранилищам стали нагляднее со структурированными биндингами (прощай boost::adapters), дедукция типов для заметно уменьшила когнитивную нагрузку (std::array {1,2,3,4} сам выведет и тип и размер). Замечательные <optional>, <string_view> и (наконецта!) <filesystem>.
C++20 живьем пока не встречал.

Folded expression сильно упростила во многих местах использование variadic template'ов.

У нас нет столько собственных шаблонов, так что конкретно это профита не добавило. std::any по той же причине не попало в мой список — везде boost::any и переход на std::any пока не планируется.

НЛО прилетело и опубликовало эту надпись здесь

Это просто перевод.

Не очень понятно преимущество в виде проверок границ массивов и инициализации переменных. На C тоже можно проверять границы массивов и инициализировать абсолютно все переменные. Да, C не позволяет красиво применять оператор [] для доступа к элементу с проверкой. Но тот же CPP с его "перегрузкой" операторов вполне разрешает делать проверку с использованием оператора [], инициализировать все переменные, а также использовать умные указатели для борьбы с утечками памяти.

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


Вообще, если интересно почитать о преимуществах D перед C, то можете глянуть эту статью: https://habr.com/ru/post/276227/

Хм-м, это как это вы так «сравниваете» структуры, через memcmp() что ли? Сурово, сурово. А что, если в этих структурах, например, есть элементы с плавающей точкой, особенно с NaN'ами всякими или +0.0/-0.0? Тогда результат такого сравнения будет отличаться от поэлементного. Для поэлементного же сравнения потенциальный мусор в паддинге значения не имеет.

P.S. Прочел по вашей ссылке, что в D при сравнении структур «под капотом просто используется быстрое сравнение диапазонов памяти». Надеюсь, что это все-таки не так (ну хотя бы не всегда).

P.P.S. Да, нашел сейчас онлайн-компилятор D, проверил — по крайней мере в случае элементов с плавающей точкой вроде бы «тупое» сравнение диапазонов памяти не используется, не считается, что NaN == NaN и так далее.

https://dlang.org/spec/expression.html#equality_expressions


У типов с плавающей точкой +0.0 И -0.0 считаются равными, NaN и NaN считаются неравными. Равенство структур проверяется по равенству всех членов, если не перегружен оператор сравнения.

Я про пассаж «в С можно сравнить две, казалось бы, одинаковые структуры, но получить ложь». То, что в D сравнение структур, скорее всего таки не делается через аналог memcmp() (по крайней мере, не всегда), я и так подозревал :)

В D просто очень умный компилятор. Он, видя, что в структуре есть float, добавляет для неё реализацию xopEquals, которая сравнивает структуры поэлементно.


Сам алгоритм сравнения структур описан тут: https://github.com/dlang/druntime/blob/master/src/object.d#L1312

Ну да, как я уже сказал — я подозревал, что в D компилятор с этим справится :) Просто C — он… как бы это сказать… язык, не обремененный абстракциями by design. Поэтому если программист таки сравнивает в C структуры через memcmp(), то ему лучше бы в любом случае твердо знать, что он делает, а иначе проблемы гарантированы — будь то проблемы с мусором в паддинге, с элементами с плавающей точкой, с элементами-указателями на некоторых архитектурах (там, где указатель представлен в виде сегмент: смещение) и так далее. Паддинг, короче, не самое страшное при этом :)

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

Эм, в C тоже можно сравнивать структуру по элементам. Если нужен синтаксический сахар, то можно использовать CPP, перегрузить оператор сравнения, который будет сравнивать так, как нужно вам.
Если хотите использовать memcmp для сравнения, то C никак вам не запрещает в структурах инициализировать одним значением байты выравнивание (зачастую нулями). Да, на C это будет некрасиво, но если нужен синтаксический сахар, то есть CPP.
Вообще, плох тот язык системного программирования, который по умолчанию делает кучу ненужных вещей. Вещи, вроде, проверки границ массива, инициализации байтов выравнивания должны делаться исключительно там, где в этом есть необходимость.

Зачем использовать CPP, если есть D?
Все эти проверки и инициализации появились не просто так, а как решение самых распространённых уязвимостей.

Затем, что C даёт выбор: я могу инициализировать структуру и тогда автоматически все байты выравнивания занулятся (а также все поля, которые не были указаны при инициализации), могу не инициализировать структуру и тогда ничего зануляться не будет. В D, как я понял, если не делаешь инициализацию сам, то структура сама инициализируется нулями. В итоге будет ряд случаев, когда компилятор не сделает оптимизацию и в рантайме будет выполнен ряд лишних операций. Можно попросить компилятор кидать варнинги (а их отображать как ошибки), когда какая-то переменная не инициализирована, а в местах где инициализация не требуется ставить атрибут аля "так и задумано". С массивами тоже всё тривиально: делаем функции, которые работают с "проверенными данными" (То есть, если программист не накосячил, то выхода за пределы массива не будет) и функции, работающие с внешними данными (проверки в рантайме потребуются). Для минимизации ошибки программиста ставим ассерты и запускаем дебаг версию с санитайзерами.

D не лишает вас этого выбора.

В D, как я понял, если не делаешь инициализацию сам, то структура сама инициализируется нулями

Не нулями. Агрегатные типы инициализируются значениями по умолчанию для их полей — для double, например, это NaN, а не ноль. Можно указывать свои значения по умолчанию:


struct S { int x = 42; }
S s;
assert(s.x == 42);

При этом любую инициализацию всегда можно отключить:


struct S { int x = 42; }
S s = void;
// Теперь s.x равно бог весть чему.

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


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

Компилятор умеет оптимизировать лишние инициализации.

Ты неверно задал вопрос.

Преимущество D-betterC не только и не столько в этом. Ты при этом получаешь язык более высокого уровня с шаблонами, CTFE, ООП, юниттестами и много еще с чем… Бесплатно, и с легкой миграцией
Есть ли популярные проекты, реализованные на D? Какова его область применения?
Vulkan под D работает?
ОГРОМНОЕ СПАСИБО!
У D было много лет до взлета Go и Rust.
Но D не взлетел в отличие от вышеупомянутых. И D уже не взлетит.

И далеко они там взлетели? Вот вам рейтинг TIOBE, например:

В Тиобе Дланг проиграет обоим (рейтинг же по количеству гуглящих): Го — из-за массовости, Расту — из-за сложности — почти каждую строку приходится гуглить =)

Но я думаю, ему надо не взлетать, а найти устойчивую нишу.

Jack of all trades master of none 0\
Вот тут пишут, что D универсальный язык.
И на меньшее, видимо, несогласны.

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

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

Что за деффекты?

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

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

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

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

С++ изобрели потому, что проекты на си т е без ООП почти всегда вырастали в помойки которые невозможно развивать и даже поддерживать сложно, у молодежи этого понимания нет им проще писать функции чем думать о полиморфизме и солиде, поэтому клоны си без классов и наследования, порождающие такие же безнадёжные помойки, но не сразу и в условиях дикой ротации кадров не важно, заходят ну и конечно просто в их раскрутку вваливаются огромные ресурсы, а диланг например всегда был сам по себе

Ну то что в 1980х без ООП не получалось делать масштабируемых удобных систем не означает, что ООП в 2020 году это серебрянная пуля. Кроме шуток, наследование реализации это скорее минус, чем плюс, что было видно на протяжении эволюции от плюсов и через жабу в современные ООП языки. Динамический диспатч тоже плохая штука, плох как для понимания разработчиком что где вызывается, так и для компилятора которому это убивает 90% оптимизаций. А больше В ООП никаких своих фишек и нет.

ООП дает шанс сделать хорошо и примеров, когда у разработчиков получилось использовать это преимущество великое множество, когда же у вас отобрали ООП, исключения, аккуратную непротиворечивую нотацию этого шанса не будет даже в перспективе развития, а будет как раз больше различного «динамического диспатча». Пример из веба:
в CMS WordPress написанном на процедурном пхп4, что-бы поменять лейаут страниц (2 колонки на 3) надо хачить код на помеси хтмл и пхп прямо в темах
а в CMS LifeRay это делается в настройках лейаута страницы (или можно программно).

А почему именно ООП? Может ФП дает шанс сделать хорошо? Или RxP? Или ещё что-то?


Примеров монстров в ООП стиле мне тоже хватает, из последнего с чем я работал — PDFKit, где документ наследует класс шрифта, текста и чёрта в ступе.

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

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

Попробуйте MobX, с ним код будет ещё проще, чем со стримами, а работать будет быстрее.

оно такое может показаться проще, но на самом деле это запутывает код, там где у вас бизнес-логика в одном блоке, она будет разманазна на какие-то ассемблерные магии, разобраться в которых и с дебагером будет сложно не говоря о таких вещах, чтобы взять и сделать переход к определению того, что происходит при нажатии условной кнопки.
Rx и подобные вещи хороши пока вы код одного применения умещается на экране. Вот например вам надо сделать реализацию дранэндропа или обрабатывать ввод поля ввода. Для этого надо взаимодействовать сразу с последовательностью событий, т.е. взять 3 и если там что-то подошло под критерий (есть все из: драгстарт, драгмув, драгенд) обработать, тут rx будет удобнее и изящнее, потому что выражение примерно так и будет записано как: стрим.взять(3).офильтровать(условие).выполнить(код).запуск().
Mobx тут вероятно может быть использован как rx и тогда не ясно зачем он нужен вообще. Стримы скорее всего со временем добавят в язык и rx тоже станет лишним.
Альтернативно, он может быть использован аналогично императивному подходу с хранением промежуточных состояний, что практически всегда хуже и фактически сложнее, т.к. потребует явной проработки множества кейсов.
стрим.взять(3).офильтровать(условие).выполнить(код).запуск().

Дрегендроп так не работает.


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

Не сложнее, чем со стримами.

фп дает шанс на алгоритмическом и локальном уровне, ооп — на архитектурном.

Ээ, нет, вот прям вообще нет. ФП не является ничуть менее "абстрактным", а с системами эффектов на самом деле куда более. По крайней мере, в ООП я вот не нашел паттерна, как сделать так, чтобы один компонент был синхронный и всегда возвращал результат, второй асинхронный и всегда возвращал, третий синхронный который может вернуть ошибку, четвертый — асинхронный и может не найти объект (вернуть Option). И чтобы со всеми ними можно было работать однотипно (в терминах ООП: наследовать один интерфейс).


А потоки Rx это и есть ФП — гуглите про FRP. И я вот не вижу, где оно менее архитектурно чем какие-нибудь акка акторы/стримы или упомянутый ангуляр :)

в ООП я вот не нашел

Fibers абстрагируют от асинхронности.
Exceptions — от ошибок.


А потоки Rx это и есть ФП

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

Fibers абстрагируют от асинхронности.
Exceptions — от ошибок.

Нет, эксепшны ни от чего не абстрагируют. Абстрагируют Result и прочие способы алгебраически работать с ошибками. На эксепшнах это сделать пыталась Java с Checked, но как известно это не очень удачно вышло.


Так покажите пример. Как написать код, который проходит по списку объектов и для каждого вызывает некоторую функцию. Причем функция может быть синхронной/нет, завершатсья с ошибкой/нет, возвращать объект всегда/иногда возвращать нулл/… Чтобы это было всё в типах, конечно. Чтобы не было трай кетчей на функцию которая ошибки никогда не бросает, чтобы не было необходимости писать Promise.resolve() для функции которая всегда синхронно вовзращает результат и её искусственно натягиваете на асинхронность, ну и так далее.

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


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

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

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


Ну и вы так и не показали, как включать асинхронность на стороне клиента по-желанию, в типах.

Вы сказали, что что-то не нашли. Я вам подсказал, где искать. Дальше вы уж как-нибудь сами.


Всё нормально у многопоточки с исключениями.


Что вы имеете ввиду под "включением асинхронности на стороне клиента" вообще не ясно.

Всё нормально у многопоточки с исключениями.

Скорее всё очень плохо


Чтобы я мог написать функцию, абстрагированную от асинхронности. Чтобы можно было сделать


let foo: Something = MyFoo<Sync>();
let asyncFoo: Something = await MyFoo<Async>();

И что это? Вы код покажите, с конкретной сигнатурой myFoo которая позволяет так писать

Там есть много примеров кода и вся необходимая для его понимания теория.

НЛО прилетело и опубликовало эту надпись здесь

Ну вот подумайте, зачем, например, функции filter знать про все эти MonadError, MonadAsync, MonadLogger, MonadAnythingElse.

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь
очередной «убийца си» без классов

Вроде, классы тут есть, но без полиморфизма.

Классы с полиморфизмом есть в полноценном D. В Better C только структуры. Они не поддерживают наследование, но могут содержать методы, конструкторы и деструкторы. Есть любопытная техника, которая позволяет реализовать полиморфизм без наследования: alias this.


struct A {
    int x = 42;
}
struct B {
    A a;
    alias a this; // Теперь B позволяет обращаться к членам A как к своим собственным.
}
B b;
assert(b.x == 42);

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

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

Вот не совсем понятно, почему полиморфизм не завезли. Я понимаю dynamic downcast. Но что такого особенного требуется для vtable dispatch и интерфейсов?

Как вы из сишного кода будете делать этот vtable dispatch? Режим betterC был введён для ограничения возможностей D с целью прозрачной интеграции в программы на C. Если такая интеграция не требуется — просто пользуйтесь полноценным D со всеми его рантайм возможностями.

со всеми его рантайм возможностями.

Вот ключевое. К примеру, нужны в одном из компонентов полиморфные типы, без выставления их в С. Без рантайма это вполне делается. Чем тогда betterC лучше какого-нибудь Zig или Rust? Теряем 90% стандартной библиотеки просто потому, что она наглухо завязана на GC.

Для сишников "полиморфные типы" так же не актуальны как и сборщик мусора.


Если вам всё же это всё нужно — ну не используйте betterC, он вам, очевидно, не нужен.

Для сишников "полиморфные типы" так же не актуальны как и сборщик мусора.

Настолько не актуальны, что в каждом втором проекте велосипедят свои векторы/хэш-таблицы.


Если вам всё же это всё нужно — ну не используйте betterC, он вам, очевидно, не нужен.

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

Вы перескочили на обобщённые типы. К полиморфизму они имеют мало отношения.


Это всё замечательно, но в C это разве прозрачно интегрируется?

Посмотрите на драйверы ядра в Linux. Чем не полиморфные объекты на Си?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации