Pull to refresh
4
0
Send message

Да, магические числа (размер, выравнивание болванки std::aligned_storage - кстати, этот класс из стандарта убирают в C++23) в хедере; при несоответсвии правильным числам (размеру и выравниванию класса Impl, реализации) static_assert выдаёт правильные числа в диагностике (трюк, по-моему, в презентации Антона описан). То, что выглядит не очень - сходная цена при ограниченном применении. Я подумывал о более широком применении FastPImpl с генерацией скучного кода, но - поскольку применяю не часто, пока не сделал.

Спасибо за ссылку про модули ниже - кое-что читал по ним, но пока не играл с ними (не могу использовать на работе, и, похоже, ещё довольно долго не смогу).

С FastPImpl у меня есть опция (которую, впрочем, я пока не применял) подменять реализацию класса Xyz::Impl моком в случае, если я собираю тест для кода, использующего Xyz. Просто, вместо либы, реализующей настоящий Xyz::Impl, при линковке подставить мок-либу (ну или просто добавить в код теста #include на хедер-онли реализацию мока Xyz::Impl). Вероятно, с модулями тоже можно проделывать хаки такого рода, по крайней мере, при определённой дисциплине использования модулей.

В плане обмена опытом рекомендую посмотреть начало (и конец, с ответами на вопросы по конкретной теме) вот этой презентации, то есть, её части, посвящённые FastPImpl: https://www.youtube.com/watch?v=mkPTreWiglk&t=1046s (перемотано 2 мин 20 сек от начала - сразу к делу).

Disclaimer: я с Антоном Полухиным не знаком и к Яндексу отношения не имею, просто с интересом просмотрел эту презентацию и использовал изложенные идеи реализации Fast Pimpl, когда мне это понадобилось (не найдя ничего лучше на тот момент). Сейчас посмотрел написанный тогда код - определение template-класса FastPImpl (помощника) занимает примерно 25 строк довольно прямолинейной логики. Они включают в себя определения делегирующего конструктора и деструктора (а не операторов new, delete), проверку соответствия размера и выравнивания во время компиляции (с помощью static_assert), удобные аксессоры для использующего класса. Move-конструктор и move-присваивание тоже делегируются, а копирующие я делитнул (и до сих пор ни один из классов, скрывающих таким способом реализацию, не потребовал семантики копирования). Использую как раз в ситуациях вроде описанной вами - для изоляции публичного объявляния класса от реализации (когда это имеет смысл) с нулевым / минимальным оверхеадом (зависит от того, включена ли опция link time code generation в билде). Использующие классы - публичные интерфейсы своих скрытых за FastPImpl реализаций - имеют некоторое количество тривиального бойлерплейта, но - терпимо.

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

Оставляю терминологический комментарий: приём, с которого вы начинаете заметку, и который вы назваете "цепочкой", общепринято называется "телескопическая сумма", "телескопический ряд" (https://ru.wikipedia.org/wiki/Телескопический_ряд) - калька с telescoping sum. Имеется в виду физическая аналогия в виде старинного телескопа или складной подзорной трубы из входящих друг в друга трубок.

Да (пардон, я пропустил ваш ответ тогда), практическая польза -- очень важное мерило, я с этим согласен. Дисбаланс в наших мнениях, возможно, здесь (цитирую вашу последнюю реплику): "лично меня интересует не процесс и не все подряд результаты, а только те, которые можно применить на практике". Мне процесс и промежуточные результаты инетересны в некоторых областях безотносительно к применению-на-практике-прямо-сейчас. Упражнение ума (изредка всё-таки получается интересные идеи применять на практике самому), плюс -- элемент future proof (ориентироваться в нововведениях заранее).

Практичность, внедрение чего попроще впереди более продуманного с заделом на будущее подхода -- иногда имеет неприятный длинный хвост, опять приведу пример: дженерики в Java. Их ввели в язык с значительной задержкой (возможно, кстати, не без влияния конкурирующего C#, в котором дженерики появились раньше), и получившийся результат не изящен (читаю книгу по Kotlin - там проблему систематически исправляют, но не обходится без заусенцев на стыковках с Java). В момент разработки первой версии языка нужная теория (и практические наработки из других языков) были наготове -- но сделали (правомерно, вероятно) выбор в пользу более раннего релиза более простой, практичной системы без дженериков. То же с Go, я не слежу за ситуацией (вводят там дженерики в новых версиях или нет) -- повторение похожей истории лет 20 после выпуска первой версии Java. С C#/.NET вышло более гладко -- базу для введения более органичный дженериков теоретически разработал (и сделал прототипную реализацию) учёный Don Syme (знаменитый также, как автор и популяризатор F#) -- а стартовали, опять же, без дженериков, "практично".

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

«Алгебраические эффекты» человеческим языком - перевод на Хабре вводной статьи прикладного программиста, который ближе к концу поясняет, что с Хаскелем он не знаком и потому научные статьи ему непонятны, он рассматривает тему с практическими примерами на JavaScript (ссылка на оригинал статьи для удобства). Более недавняя статья другого программиста с примерами на OCaml: Friendship ended with Monads: Testing out Algebraic effects in OCaml for Animations.

Научные статьи, рассматривающие алгебраические эффекты и альтернативные подходы (монадные трансформеры) в рамаках Хаскеля: Monad Transformers and Modular Algebraic Effects (What Binds Them Together), Freer Monads, More Extensible Effects. Я такие статьи, скорее, просматриваю - с Хаскелем шапочно знаком, но не программирую на нём.

Уместна ссылка на давнюю статью автора Koka: Algebraic effects for Functional Programming.

Моё поверхностное мнение: мне бы на Koka было бы удобнее программировать, чем разбираться с монадными трансформерами на Хаскеле. Koka - компактный язык, чётко иллистрирующий идею как "чисто" программировать в функциональном стиле с эффектами ("чисто" в том смысле, что нотация эффектов явная, и при этом не особо навязчивая). В плюс к этому - такое программирование хорошо укладывается у меня в голове (я "вижу", что будет происходить в машинном коде при выполнении - и при желании могу разобраться в сгенерированном компилятором Koka коде на стандартном C, реализуещем нужную семантику благодаря фиче C longjump, насколько я понимаю). Ну и -- при желании я этот код на C мог бы встроить в свою программу (на C++); не уверен, что получилось бы легко с встраиванием кода на Хаскеле.

С Rust сравнивать, наверно, можно, но осторожно - размах не тот (Rust - промышленный язык, Koka - скорее, демонстрационный, ну, как сравнивать Rust и Idris).

Пока нет, но вы оборвали цитирование.

... "However, we have plans for future improvements: since we know statically that only mutable references are able toform a cycle, we could generate code that tracks those datatypes at run time and may perform a more efficient form of incremental cycle collection."

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

Если вы продолжаете работать над проектом, обратите, пожалуйста, внимание на правильность английского в идентификаторах, названиях файлов - IsXyzExists плохо звучит. Уберите.Is или замените на Does, убрав s на конце (DoesXyzExist).

(я не автор поста) По-моему, смысл & - создать временный объект ValuePointer, с вызовом guard в конструкторе и unguard в деструкторе. Ну а * - собственно взять завёрнутое в этот временный ValuePointer значение, но! - с дополнительными приспоблениями, реализованными в guard / unguard (ну, потоковая безопасность, что-то такое). Польза!

Конечно, невнимательный прикладной программист может случайно забыть добавить *& -- и не получить пользы. Код будет как бы работать, но не совсем: что-то крэшнется, что-то полетит не туда. Ну а кто сказал, что профессия программиста - простая и лёгкая? Программист должен быть внимательным!

В pdf-е этого переиздания не хватает обложки с портретом Киселёва - её можно увидеь здесь (издание - не коммерческое, стоимость бумажной копии равна затратам на производство этой копии на одном из print-on-demand сервисов):
https://www.amazon.com/Геометрия-Киселёву-Russian-Петрович-Киселёв/dp/1684748127/

Для полноты (на случай, если кому-либо окажется полезным) - вот современное переиздание учебника Киселёва: https://arxiv.org/abs/1806.06942

Переводчик вам вряд ли ответит на вопрос к автору - они работают в разных фирмах и вряд ли общаются напрямую. В заметке есть ссылка на следующий пост автора, посвящённый именно тестированию:
https://betterprogramming.pub/quit-unit-testing-classes-and-use-a-behavior-oriented-approach-306a667f9a31
Кратко - на юнит тестирование забили большой болт, но, вроде как, выстроили супер-дупер систему интеграционного тестирования. По мне - есть риск, что когда аторы супер-дупер системы интеграционного тестирования переместятся на более зелёные пастбища, она заржевеет, и следующий набор сотрудников будет или её переписывать, или таки-восстанавливать возможность юнит-тестирования. Моё персональное мнение, ни в коей мере не претендующее на близость к реальности, так красиво (с картинками!) изображённой автором.

Отличная практика в проекте, в котором вы владеете coding guidelines и можете в одночасье расставить везде const, да ещё и поддержать практику статической проверкой (такие инструменты есть). В Rust то, что неизменяемость идёт по дефолту (ничего не надо писать, наоборот, надо писать mut для изменяемости) - замечательно. А в C++ дефолт, увы, противоположный. Согласен с спецификацией const для не-мутирующих функций-членов, но вот, const для каждой локальной переменной (и параметров, переданных по значению) - редко встречаю такое, так что ваш тезис мне понятен, но сам на практике предпочёл в данный период не применять (пока - из-за отсутствия технической возможности поддержать статическим анализом). Когда под рукой будет на готове опция статической проверки (что все немутирующие переменные и методы помечены const) - обсужу с коллегами и, возможно, начнём внедрять. В соло (хобби-)проектах - тоже за! Но, в коллективном проекте с большим объёмом кода без соблюдения этого правила и без возможности поддержки правила статическим анализом - взвесив за и против, решил пока так. Первый приоритет для меня - получить возможность обеспечить такую проверку статическим анализом (до этого - нет смысла и обсуждать с коллегами!), потом - достичь консенсуса с коллегами, что они согласны - эта новая строгость пойдёт на пользу в перспективе, а потом уже - внедрять. Внедрять на существующем коде, где такого правила нет, без поддержки автоматической проверкой - гиблый номер.

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

Что вы говорите, пишете коллеге, реализовавшем цикл, в котором непонятно, почему произойдёт завершение, и будет ли при завершении гарантирован какой-либо результат?

Используете ли вы в таком случае в диалоге термины вроде "инвариант цикла", "условие завершения"? Не предлагаете ли вы такому коллеге от греха подальше пользоваться более выскоуровневыми конструкциями типа range loop (специально проверил, что в Go есть такой)?

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

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

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

Обратный пример, отрицательный, но он вам должен быть близок. Perl - замечательный язык программирования, с высокоуровневыми конструкциями и компактной записью программ. Практичный (мой любимый пример - в "научной" терминологии ассоциативный массив, словарь или map - честно назван в ядре языка hash, ну чтобы сразу практичному программисту было понятно, что там внутри). У него были замечательные перспективы, активная аудитория практикующих программистов. На нём было легко создавать работающие программы! Более того, этот язык был создан начинающим программистом - и исходя из его практических потребностей (замечу, эта история создания языка программирования отличается от истории создания Go). Когда и что с Perl пошло не так, как вы считаете? Я недавно узнал, язык переименовали... зачем - может быть, чтобы сбросить балласт каких-то отрицательных коннотаций? Но что конкретно?! У вас есть мнение на этот счёт? Мне, честно говоря, Perl не нравился, но я никогда на нём не программировал, так что моё мнение - неквалифицированное. Однако, могу вздохнуть свободно - ныне это название языка могу смело выкидывать из головы (а переименованное даже не трудиться запоминать).

Мне стало любопытно - первым делом нашлась документация о том, как это делается с Coq:

https://www.cis.upenn.edu/~plclub/blog/2020-10-09-hs-to-coq/

Хорошо, давайте обсудим, и я извиняюсь за эмоциональность. Давайте оставим в стороне кейс с glibc. Я поясню, почему интересуюсь именно TLA+ (по названию инструмента нашёл кейс).

Один повод - тот факт, что высоконагруженная система не всегда даёт лучшие показатели с высокоуровневыми примитивами параллелизма. Приходится использовать lock-free структуры данных (не знаю, как с ними в Go, на C в стандартную библиотеку не входят), и я не уверен, что мне не придётся ловить / отлаживать баг, подобный описанному в том блог посте про glibc. Да, пока обходился без глубокого знакомства с инструментом, но знаю, что он использовался неподалёку - именно из-за сложностей с редко воспроизводящимися, но причиняющими постоянную боль багами с параллелизмом.

Далее, любопытный фрагмент истории инструмента (тут я могу быть не вполне точным, печатаю без проверки каждого фактоида). TLA+ разработал Лесли Лампорт (автор среди прочего LaTeX, но в данной истори более кстати тот факт, что он автор алгоритма distributed consensus под названием Paxos). Ему нужно было средство автоматической верификации алгоритмов такого рода и, скорее всего, ни одно из существующих на тот момент не подходило. Реализовав инструмент, он написал книгу, поясняя, как им пользоваться (также, как до этого написал книгу про LaTeX). Другой автор продолжил (хоть инструмент старый, недавняя книга вышла в 2018-м). Короче, я решил хотя бы шапочно познакомиться с TLA+, буду готов, если прийдётся применять (ну и, интересная тема - в школе и институте приходилось формально корректно доказывать теоремы на зачёт / сдачу экзамена).

Давайте для проверки тезиса, что алгоритм (довольно базовый - что может быть базовей достижения консенсуса в распределнной системе, да?) такого класса не будет воспринят серьёзно без формальной верификации, рассмотрим более недавнюю альтернативу Paxos-у, Raft. Да, быстро нашлась статья по верификации Raft-а - верифицировали с использованием Coq, кстати (как в работе автора статьи, в комментариях к которой мы находимся).

Нужно ли это программистам в повседневной работе? Хорошо, если нет! Но, когда всё-таки "не повезло" и таки-нужно, улучшение инструментов не помешает.

Вспомнился другой случай, более прозаический - баг в стандартном алгоритме сортировки Python, TimSort (используемом также в Java-системах, в стандартных коллекциях). Баг существовал в коде алгоритма (портированного вместе с багом для Java) лет 12-13, прикидочно. Его нашли и исправили только тогда, когда computer scientist-ы (уровня аспирантов, я так понимаю) попробовали верифицировать алгоритм (в рамках более крупного проекта верификации стандартных библиотек Java). На Хабре были заметки про это, вот, например (переводная): https://habr.com/ru/post/251751/. Баг, скорее всего, проявлялся довольно редко. Тем не менее, лучше всё-таки, если он исправлен, и корректность исправленного алгоритма, наконец, доказана? Это подводит к тезису, что доказывать корректность стабильных базовых библиотек (в частности, реализующих высокоуровневые примитивы для многопоточного программирования) - неплохая идея.

Насколько это дорого? Учёные делают это "за кредиты" (статьи), и при этом улучшают инструменты (о чём статья, в комментариях к которой мы находимся). Иногда за инструмент приходится браться программисту (пример - случай с glibc). Дороговизну применения инструмента в конкретном случае следуют делить на массовость применения проверяемого алгоритма с учётом возможной высокой стоимости его отказа. Иногда знаменатель оказывается достаточно большой.

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

Всего доброго.

Что вы, наличие более простого способа решения проблемы - всегда хорошая новость! Жаль, никто не смог в данном случае этого сделать... Так что, увы, хорошей новости не случилось.

В этом случае (deadlock баг в реализации conditional variable в glibc), похоже, простой способ почему-то не сработал. Сложный - сработал. И это не единственный случай, когда для решения реальной проблемы пришлось прибегнуть к сложному способу.

Для меня это (необходимость применения в некоторых сложных случаях сложных способов) - не новость. Для вас - плохая новость?

Information

Rating
5,486-th
Registered
Activity