LINUX.ORG.RU

Функциональщина на C++

 , ,


0

5

По мотивам: Си с классами

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

Как эффективно учиться? (комментарий)

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

Конкретно мне интересен функционально-процедурный подход к написанию кода на крестах, что-то похожее на Rust, только без абсурдной помешанности на безопасности памяти, так сказать «си с плюшками», но совсем НЕ «си с классами», как было в упомянутом треде. Для примера: Qt и UE — это примеры плохой архитектуры в данном контексте. Например, fstream — это плохая реализация файловых операций, поскольку скатывается в классы и исключения, в ней даже нельзя без исключений получить конкретную ошибку файловых операций.

Итак: какие есть конкретные хорошо проработанные приемы и библиотеки для писания на крестах в функционально-процедурном стиле?

★★★

Ответ на: комментарий от anonymous-angler

Справедливости ради, в расте нет подобного уродства.

Да, там дошколята уже поняли(ну как поняли - так им сказал хозяин), что обгадились и теперь там везде исключения через unwrap. Ждём пока то же до си с классами обезьян дойдёт.

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

Можно стек трейс скинуть в лог

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

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

Когда у тебя код завален сверху до низу блоками try-catch — этого еще мало. Нужно все эти комбинации блоков оттестировать. И сложность подобного тестирования астрономическая, поскольку упущен важный прием — сведение многих разнородных ошибок в малое число ветвей выполнения. То есть, в какой-нибудь сихе уже снизу в коде идет сокращение вариативности выполнения. Код на исключениях занимает позицию «разберямся с ошибками как-нибудь потом, наверху». И получается лапа, когда высокоуровневый код обрабатывает низкоуровневые сущности, наоборот, низкоуровневые в высокоуровневые, и просто разнородные модули лезут в логику друг друга.

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

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

Я бы сказал так: в противоположность, существует другой подход, когда программист пишет программу, которая ничего не делает — это функциональный подход. Чтобы программа выполнила соотношения в функциях, нужен императивный код. Потому функциональное программирование без императивного не работает. Это не противоположности, а разные приемы абстракций.

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

Скорее как «мыть руки мылом без воды». Берешь и натираешь руки мылом, чо.

В контексте императивного/функционального программирования функция != процедуре, потому что функция имеется в виду математическая, которая чистая, а не функция из информатики, которая та же процедура только с возвратом, она же подпрограмма

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

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

Не называли, были отдельно процедурные языки, и отдельно функциональные, с системой Хиндли-Милнера, с удобными фвп и с минимизацией использования изменяемых переменых. Типичные представители ML семейство и hope, появились уже в 70 годы, задолго даже до прородителя хаскеля и почти одновремено с си и паскалем

Standard ML уже имел поддержку изменяемых данных, пусть их и нужно было объявлять явно, как в Rust. Но от того, что их нужно объявлять явно в Rust, он же не становится «отдельно функциональным языком»?

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

Она оптимизирована под большие объемы хранимых и обрабатываемых данных. Там Git-подобная (бранчи, коммиты) модель персистентности, и в рамках одного коммита можно группировать сколько угодно апдейтов в разные контейнеры

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

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

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

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

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

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

То есть авторы стандартной библиотеки C++.

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

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

Ну и да, ты даже здесь обгадился, поскольку твоя потуга всегда будет выдавать «operation not permitted, code 1»

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

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

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

Как ты собрался с таким подходом писать сервер БД? Или какой-нибудь systemd. Или любую другую программу с долгоиграющим состоянием — да хотя бы электронную таблицу. В программе очень много модулей, если ты не будешь в вызовах между ними корректно обрабатывать ошибки, то твоя программа приблизится на практике будет близка к состоянию «полностью неработоспособна». Конечно, не полностью, но разница эта незначительна.

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

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

https://en.cppreference.com/w/cpp/container/vector/at — Это тоже по-твоему «всё, приехали»?

Самая главная же претензия к fstream — это что без включения исключений код ошибки нельзя гарантировано получить штатными интерфейсами. У того же std::filesystem есть два варианта функций, с исключениями и без, а вот у fstream такого нету.

byko3y ★★★ ()
Ответ на: комментарий от no-such-file

В крестах же положено вообще все ошибки через исключения бросать

Ну как бы там без вариантов. Из конструктора кроме как исключением ошибку никак не вернуть

Вариант есть — не пользоваться конструктором. Даже с конструктором есть третий вариант — конструировать пустой объект и отдельно его инициализировать, как сделал разраб ZeroMQ.

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

К сожалению, на больших объемах неизбежно возникает необходимость отказа от строгой целостности.

Это не совсем так. Отказ от строгой целостности нужен только тогда, когда тебе нужна «высокая скорость». Что само по себе сильно зависит от задачи.

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

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

Я могу потом показать, как это делается в Мемории. Пока этот функционал еще не реализован.

aist1 ★★ ()
Ответ на: комментарий от no-such-file

Ну как бы там без вариантов. Из конструктора кроме как исключением ошибку никак не вернуть.

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

(Но там я как раз постепенно выпиливаю коды ошибок в пользу исключений).

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

https://en.cppreference.com/w/cpp/container/vector/at — Это тоже по-твоему «всё, приехали»?

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

Как ты собрался с таким подходом писать сервер БД? Или какой-нибудь systemd. Или любую другую программу с долгоиграющим состоянием — да хотя бы электронную таблицу. В программе очень много модулей, если ты не будешь в вызовах между ними корректно обрабатывать ошибки, то твоя программа приблизится на практике будет близка к состоянию «полностью неработоспособна». Конечно, не полностью, но разница эта незначительна.

Ты меня не понимаешь. Вот если у меня телефон накроется, то я не буду выпаивать SMD конденсаторы/резисторы и искать битый, я просто другой возьму. Аналогично прилёт исключения откуда-то - этот модуль/systemd/БД - всё, не желец. Тут можно упасть с коркой, выполнить задачу заново, я не намерен выяснять в ран тайме причину и пытаться лечить её. Для остального есть коды возврата.

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

Серьезное ограничение всех ФСД/ПСД, о которых не говорят их пропоненты — это необходимость в сборщике мусора

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

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

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

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

я не намерен выяснять в ран тайме причину и пытаться лечить её

Ну с тобой-то всё понятно. Вопрос-то был про нормальных программистов:

Как ты собрался с таким подходом писать сервер БД? Или какой-нибудь systemd. Или любую другую программу с долгоиграющим состоянием — да хотя бы электронную таблицу.

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

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

Не, только не в ПСД. Здесь у тебя есть SnapshotID/CommitID и ты всегда читаешь точно определенную версию данных (point in time), если она есть на этом шарде. И ты точно знаешь про это, если этой версии там нет (выпилена сборщиком мусора или удалена руками).

Я делал на основе старой версии Мемории облачное хранилище для экспериментальной аналитической системы. Больше всего фронтендерам нравилась снэпшот-семантика. Когда у тебя есть SnapshotID, тебе не надо париться, что данные изменятся, пока юзер навигирует между формами приложения.

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

https://en.cppreference.com/w/cpp/container/vector/at — Это тоже по-твоему «всё, приехали»?

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

vector::at имеет вполне конкретное отличие от простого доступа по индексу, которое как раз и заключается в том, что выход за границы буфера корректно обрабатывается. Однако, обработка эта идет через исключение, а не через возврат ошибки — что противоречит твоему тезису про «всё, приехали». Операции в std::filesystem аналогично сыпят исключения с расчетом на то, что онные будут перехвачены.

Вообще, изначально для интерактивных систем подразумевалось, что исключения будут выкидывать куда-то в главный цикл для выдачи пользователю ошибок вроде «файл занят». Но очень быстро выяснилось, что по мере роста сложности пользователь начинает получать невнятные «bus error» и «range overflow», а чтобы ошибки стали внятными нужна... явная обработка ошибок.

Вот если у меня телефон накроется, то я не буду выпаивать SMD конденсаторы/резисторы и искать битый, я просто другой возьму. Аналогично прилёт исключения откуда-то - этот модуль/systemd/БД - всё, не желец. Тут можно упасть с коркой, выполнить задачу заново, я не намерен выяснять в ран тайме причину и пытаться лечить её. Для остального есть коды возврата

Потому конденсаторы и резисторы в телефоне делаются так, чтобы не ломались. Ровно как и важные резидентные сервисы — в проде они, как правило, не падают. А если пару раз упадут, то кто-то будет уволен с работы за некомпетентность. Ошбика выясняется не в рантайме, а во время разработки — находятся все потенциальные ошибки, которые могут произойти в программе. И по такой модели в C++ разрабатывать программу очень сложно, по крайней мере без переписывания стандартной либы.

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

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

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

Вариант есть — не пользоваться конструктором

Есть вариант ещё лучше, не пользоваться C++. Бери голанг, например.

no-such-file ★★★★★ ()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от aist1

Можно ошибку специальным out-параметром возвращать из конструктора

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

no-such-file ★★★★★ ()
Ответ на: комментарий от aist1

Здесь у тебя есть SnapshotID/CommitID и ты всегда читаешь точно определенную версию данных (point in time), если она есть на этом шарде. И ты точно знаешь про это, если этой версии там нет (выпилена сборщиком мусора или удалена руками)

Читаешь — да. Вот с модификацией уже не всё так просто, поскольку этот SnapshotID может оказаться тухляком, даже если на момент создания снимка он был самым последним. В случае read-only данных-то всё просто, тут вопросов нет, этот SnapshotID можно создать один раз при запуске клиента и не менять хоть целый день, как в межбанковских платежах.

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

Так умеет делать даже MySQL и PostgreSQL с темпоральными расширениями. Странно, что эта фича еще не в стандарте SQL.

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

А вот это странно, на самом деле... Откуда у тебя взялась такая большая потребность в метапрограммировании?

Годное замечание. Я поясню.

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

Пример: std::map<K, V> против костыляния чего-то похожего макросами на С. Если кто не знает, то кардинальность множества структур данных намного выше кардинальности множества алгоритмов, поэтому нам и нужно обобщенное программирование, которое является подклассом метапрограммирования. Поэтому, когда кто-то, например, говорит, что «нет ничего лучше, чем С», то он, в лучшем случае, имеет дело с очень ограниченным множеством типов (структур) данных, и начал жить в этом пузыре.

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

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

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

В случае ПСД, кстати, всё еще хуже, так как CoW-историю очень трудно преобразовывать. Поэтому низкоуровненые форматы данных нужно выбирать оооочень тщательно.

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

Ну если ты пишешь «systemd» и после прилёта исключения «Exception_from_Vasya» из модуля №3, ты пытаешься его реанимровать и оживить вместо - отстегнуть и забыть, то с тобой мне тоже всё ясно

Падение systemd недопустимо, как и «отстегнуть и забыть» — просто потому, что вместе с ним валится вообще всё. Да, сегфолт нежелателен, потому код нужно писать так, чтобы сегфолтов не получалось. А от ответа ты уходишь.

byko3y ★★★ ()
Ответ на: комментарий от no-such-file

А как быть, например с конструктором копирования? Он ведь предполагает определённую сигнатуру

Копирование можно запретить. С перемещением чуток попроще будет, тем более, что компилятор в куче случаев перемещение просто устраняет, особенно в STL начиная с C++11, где потребность в копировании сильно ниже, чем в более ранних версиях.

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

Читаешь — да. Вот с модификацией уже не всё так просто, поскольку этот SnapshotID может оказаться тухляком, даже если на момент создания снимка он был самым последним.

Так не удалять его. Назначить retention policy в одну неделю, и тут уж точно все его увидят. Дело в памяти под хранение версий. ПСД/CoW позволяют достичь изоляции, но за это придется платить памятью под эти версии. Но фишка в том, что лучше иметь версии «из коробки» и платить памятью, чем костылять это всё самим на разных уровнях.

Странно, что эта фича еще не в стандарте SQL.

Там не всё так просто. И я бы и близко не ставил то, каким образом реализуются темпоральные расширения в RDBMS с CoW-based PDS.

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

Пример: std::map<K, V> против костыляния чего-то похожего макросами на С. Если кто не знает, то кардинальность множества структур данных намного выше кардинальности множества алгоритмов

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

Поэтому, в больших системах ETL обычно играет ключевую роль, почему data engineering и выделился в отдельный тип задач

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

Метапрограммирование нужно для того, что управление всеми этими форматами данных (раскладками данных по памяти) «вручную» становится задачей запредельной сложности

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

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

а чтобы ошибки стали внятными нужна... явная обработка ошибок.

Еще можно просто обвешивать исключение контекстом по мере его продвижения вверх по стеку. Примерно как в Boost Exceptions. Для меня работает нормально.

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

Читаешь — да. Вот с модификацией уже не всё так просто, поскольку этот SnapshotID может оказаться тухляком, даже если на момент создания снимка он был самым последним.

Так не удалять его. Назначить retention policy в одну неделю, и тут уж точно все его увидят. Дело в памяти под хранение версий. ПСД/CoW позволяют достичь изоляции, но за это придется платить памятью под эти версии. Но фишка в том, что лучше иметь версии «из коробки» и платить памятью, чем костылять это всё самим на разных уровнях

Я немного о другом. Например, два клиента снимают по $100 со счета, на котором всего осталось $100. Или кто-то удаляет пустую группу, когда одновременно другой клиент помещает объект в эту группу, в результате чего после слияния правок объект оказывается потерянным. Аналогично с проверкой уникальности.

upd:

Там не всё так просто. И я бы и близко не ставил то, каким образом реализуются темпоральные расширения в RDBMS с CoW-based PDS

Стоп, а в чем разница? Разве RDBMS не аналогично через подобие CoW реализовывают темпоральность?

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

а чтобы ошибки стали внятными нужна... явная обработка ошибок.

Еще можно просто обвешивать исключение контекстом по мере его продвижения вверх по стеку. Примерно как в Boost Exceptions. Для меня работает нормально

Для диагностики/отладки? Да, согласен, в таком случае удобно. Если же ошибку нужно обработать автоматически, для реакции или для выдачи простого читаемого сообщения, то я не уверен, что Boost.Exceptions даст какую-то пользу.

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

А в SuperReaderWriter можно отстегнуть и забыть throw’щий плагин, один-один. И в 90% софта допустимо просто завалиться с abort() при какой-то сильно непонятной ситуации, на этом Раст стоит. И вообще ты толковал про модули, а не сам systemd, я в его архитектуру не вникал, но звучит как что-то сбоку.

В общем не всем плюсы нужны, переубеждать не буду.

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

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

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

И тут модель вложенных связных списков, которую тебе дает С или навязывает Java будет работать очень плохо. А в С++ можно через templated value object и Standard Layout генерировать какие угодно физические раскладки данных по адресному пространству.

С появлением распределенных колоночных хранилищ ETL отходит на задний план

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

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

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

Понимаю. Я поясню. ПСД в closure для main memory, причем там и близко нет никакой mechanical sympathy. И совсем другое дело, когда ввиду особенностей нижележащей модели памяти ты не можешь использовать простую и красивую технику вложенных спсиков для композиции. Особенно это касается ПСД, где всё нужно делать деревьями.

У меня сейчас нет удобной доки в проекте, но я, в принципе, могу показать, каким образом я с помощью TMP добиваюсь композиции примитивных структур данных в «много-стримовые» CoW-деревья. Причем это работает для внешней блочной модели памяти, а не только для кучи.

TMP этот получается весьма забористый и трудный в сопровождении. Но он таки свое дело делает. Хотя я и начал его заменять на питон+clang.

Есть такая штука: software construction. И есть техника design space exploration. Мемория мне позволяет делать design space exploration (DSE) для продвинутых структур данных методами software construction, реализуемых с помощью того же TMP.

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

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

Я немного о другом. Например, два клиента снимают по $100 со счета, на котором всего осталось $100. Или кто-то удаляет пустую группу, когда одновременно другой клиент помещает объект в эту группу, в результате чего после слияния правок объект оказывается потерянным. Аналогично с проверкой уникальности.

Ок. Транзакционность в разделяемой памяти — это проблема. Тут просто все реплики должны быть согласны относительно «объективной реальности» (деньги переведены) и мы не можем двигаться вперед пока они в согласие не придут.

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

Стоп, а в чем разница? Разве RDBMS не аналогично через подобие CoW реализовывают темпоральность?

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

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

в котором основной подход - unwrap, т. е. те самые исключения

unwrap используется только тогда когда ожидается что ничего кроме паники сделать нельзя или тупо в прототипах и примерах кода. Да и то в реальном приличном коде именно unwrap очень редок, есть нормальные замены или в виде функций типа unwrap_or_else или сахар вида ‘?’.

anonymous ()
Ответ на: комментарий от aist1

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

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

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

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

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

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

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

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

Опять таблетки забыл принять, или в деревне тебе уже перестали считать забавным и мелочь больше не накидывают? Ты запусти на ЛОРе фандрайзинг - мы тебе на лечение скинемся)0

anonymous-angler ()
Ответ на: комментарий от aist1

Там речь шла про композицию структур данных.

Верно.

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

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

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

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

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

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

Как? Вот есть у меня сигнатура из некоторой условной библиотеки:

int mylib_map_add(map_t* map, void* key, void* value);

Как мне тут композицией контейнеров управлять? Тут только поинтеры в кучу (вложенные связные списки). Никакой тебе кэш-локальности.

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

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

aist1 ★★ ()
Ответ на: комментарий от ya-betmen

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

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

С даёт модель памяти, аналогичную аппаратуре.

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

anonymous ()
Ответ на: комментарий от byko3y

Я немного о другом. Например, два клиента снимают по $100 со счета, на котором всего осталось $100.

Дополню. Эмулировать транзакционные приложения на ПСД можно, но они для этого, вообще говоря, не оптимизированы. Есть частные случаи — например очень шустрая LMDB именно из-за минималистичности CoW, но там один писатель может быть в каждый момент времени (SWMR). Это отлично работает для коротких транзакций типа перевода денег со счета на счет, так как транзакции получаются очень быстрыми. И плохо работает, когда нам нужно, например, сначала много прочитать, а потом что-то записать. SWMR-схема отлично подходит для всяких строго-консистентных примитивов типа очередей, счетчиков и т.п. Но не для БД в общем случае.

В общем случае делать синхронизацию распределенных транзакций через ПСД может оказаться плохой идеей. И тут старые добрые RDBMS будут рулить еще долго.

ПСД отлично подходят для гибиридных аналитико-транзакционных приложений, где есть изменяемые во времени данные, но нужен быстрый доступ на чтение к иммутабельным версиям (point-in-time).

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

aist1 ★★ ()
Последнее исправление: aist1 (всего исправлений: 1)

Короче, я пока что резюмирую немного.

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

Мемория — не совсем ФСД, так как оптимизирована под большие данные и гетерогенные вычисления, и её API будет плохо ложиться на классические функциональные алгоритмы, но это такое. Можно алгоритмы и переписать, если очень понадобится. А можно и реализовать функциональный уровень как строенный DSL для С++, а-ля Tensorflow. Это может иметь смысл, в зависимости от задачи.

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

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

aist1 ★★ ()