ненормальные нормальные алгорифмы
От: Кодт Россия  
Дата: 01.05.26 20:23
Оценка: 59 (3)
Вот давеча LaptevVV спрашивал про constexpr, так у меня есть кое-чего.
Хочу показать свою заготовку.
Пилю библиотеку Нормальных Алгорифмов Маркова во время компиляции.
https://github.com/nickolaym/nenormal

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

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

Пока всего этого нет, — статьи тоже нет )))
Но я обещаю, будет!



Что это и зачем

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

2) Во время компиляции использовать динамические контейнеры можно ОЧЕНЬ ограниченно, поэтому сделал всё на чистых константах времени компиляции, для этого нужны зависимые типы.

3) Циклы на полиморфных типах невозможны, поэтому сделано всё на рекурсии. Но рекурсия — ограниченный ресурс компилятора (да и рантайма), поэтому применил лайфхаки по развёртыванию циклов.

Сейчас машина допускает программы неограниченной длины (300 правил как нефиг делать), работающие не более 9000 итераций (глубина 900 помножить на развёртывание цикла на 10 шагов).
Увеличить время работы можно экспоненциально!!! (сделать цикл саморасширяемым), но это в конечном счёте убьёт компилятор.



Пример программы
constexpr auto executor = machine_fun<
  rule_loop<
    rules<
      rule<str{"hello"}, str{"privet"}, regular_state>{},
      rule<str{"world"}, str{"mir"}, regular_state>{}
    >{}
  >{}
>{};
// правила внесены как аргументы шаблона, а не как члены кортежа. всё равно типы зависимые, поэтому члены-данные - избыточны

constexpr auto input_value = str{"hello world!"}; // строка времени компиляции - здесь это константный массив (длина выведена из литерала)
constexpr auto input = ct<input_value>{};         // константа времени компиляции с уникальным типом

constexpr auto result = executor(input); // в constexpr-функцию (operator()) передали истинную константу и получили на выходе истинную константу
constexpr auto result_value = result.value; // сама строка лежит в статической константе

static_assert(result_value == str{"privet mir!"});


В порядке безумия я там даже умножение и деление на марковских алгорифмах забабахал. Последовательность Коллаца.
Работает! Только компилируется около полутора минут.
Перекуём баги на фичи!
Отредактировано 01.05.2026 20:26 Кодт . Предыдущая версия .
Re: ненормальные нормальные алгорифмы
От: rg45 СССР  
Дата: 02.05.26 09:19
Оценка: 37 (1)
Здравствуйте, Кодт, Вы писали:

К>Понятное дело, что это извращения с кодом, и вообще, когда допилю, (надеюсь, к Дню Программиста), сделаю статью на хабре.

К>Но сейчас мне просто нужна обратная связь по коду.

template<Rule auto... ps> struct rules {
    REPRESENTS(Rule)

    constexpr RuleOutput auto operator()(RuleInput auto t) const {
        return (not_matched_yet{t} >> ... >> ps);
    }
};


Вот в этом месте я бы не поленился и рядом с шаблоном класса, определил бы ещё и шаблон объекта класса:

template<Rule auto... ps> struct Rules {
   // . . . 
};
template <Rule auto... ps> constexpr Rules<ps...> rules.


Тогда при использовании rules можно будет убрать мозолящие глаза пустые фигурные скобки (инициализаторы).

А в нешаблонных классах имя класса можно и вовсе опускать:

struct {
   // Здесь функциональные операторы - сколько угодно и какие угодно.
} inline constexpr foo;


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

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

template<Str auto s, Str auto r, rule_state_t state> struct Rule {
// . . .
};
template<Str auto s, Str auto r> constexpr Rule<s, r, regular_state> rule;
template<Str auto s, Str auto r> constexpr Rule<s, r, final_state> final_rule;


P.S. Вообще подход полезный, как альтернатива традиционному рантайм полиморфизму — для декомпозиции и структурирования кода, для разложения сложных задач на более простые подзадачи и стратегии. Это потом легко мокается и покрывается юнит тестами.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 02.05.2026 10:35 rg45 . Предыдущая версия . Еще …
Отредактировано 02.05.2026 10:23 rg45 . Предыдущая версия .
Отредактировано 02.05.2026 10:23 rg45 . Предыдущая версия .
Отредактировано 02.05.2026 10:22 rg45 . Предыдущая версия .
Отредактировано 02.05.2026 10:03 rg45 . Предыдущая версия .
Отредактировано 02.05.2026 9:56 rg45 . Предыдущая версия .
Отредактировано 02.05.2026 9:39 rg45 . Предыдущая версия .
Отредактировано 02.05.2026 9:20 rg45 . Предыдущая версия .
Re[2]: ненормальные нормальные алгорифмы
От: Кодт Россия  
Дата: 02.05.26 10:26
Оценка:
Здравствуйте, rg45, Вы писали:

R>Вот в этом месте я бы не поленился и рядом с шаблоном класса, определил бы ещё и шаблон объекта класса:

R>Тогда при использовании rules можно будет убрать мозолящие глаза пустые фигурные скобки (инициализаторы).

Была такая мысль, но в какой-то момент, на какой-то черновой итерации я её выбросил, а потом забыл вернуться.

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

Если это сократит время компиляции, — то добавлю.

TODO проверить выгоды от констант.

R>А в нешаблонных классах имя класса можно и вовсе опускать:

R>Такие функциональные объекты могут быть как параметрами обычных функций, так и параметрами шаблонов.

Я таким образом делаю инлайновые классы, — например, в макросе NAMED_RULE.
Но тут именованность как раз является фичей, потому что читать бесконечные test_blablabla::unnamed::auto123::..... — мучительно.

TODO прикрутить туда автоматические имена (с помощью __LINE__ и __COUNTER__).
И автоматически именовать RULES, опять-таки, чтобы сокращать выхлоп ошибок компилятора.

R>В итоге получается код аккуратнее и чище.


Чистить код у меня тоже в планах )))
В первую очередь это proof of concept.

Буквально на днях я проверил концепцию "что если переписать всё на монаде Either", и оказалось, что это ОЧЕНЬ плохо, компиляция улетает в небеса.

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

R>P.S. Вообще подход полезный, как альтернатива традиционному рантайм полиморфизму — для декомпозиции и структурирования кода, для разложения сложных задач на более простые подзадачи и стратегии. Это потом легко мокается и покрывается юнит тестами.


А разверни тему, пожалуйста.
Перекуём баги на фичи!
Re[3]: ненормальные нормальные алгорифмы
От: rg45 СССР  
Дата: 02.05.26 11:26
Оценка: 37 (1)
Здравствуйте, Кодт, Вы писали:

R>>P.S. Вообще подход полезный, как альтернатива традиционному рантайм полиморфизму — для декомпозиции и структурирования кода, для разложения сложных задач на более простые подзадачи и стратегии. Это потом легко мокается и покрывается юнит тестами.


К>А разверни тему, пожалуйста.


Пишу набросок налету, без проверок компилятором, но идея будет понятна, я думаю:

// Раскладываем сложную задачу на подзадачи
template <
   Subprocessor1 P1
,  Subprocessor2 P2
,  Subprocessor3 P3
,  Strategy1 S1
   // Можно использовать параметры по умолчанию
,  Strategy2 S2 = DefaultS1
,  Strategy3 S3 = DefaultS2
>
struct Processor {
   P1 subprocessor1;
   P2 subprocessor2;
   P3 subprocessor3;
   S1 strategy1;
   S2 strategy2;
   S3 strategy3;

   auto operator()(/* . . . */) const {
      // Имплементация логики верхнего уровня с использованием подчинённых процессоров и стратегий
   }
};

// Продакшн версия.
// Обратите внимание, что нет необходимости указывать параметры шаблона явно.
inline constexpr Processor process{
   .subprocessor1 = process1
,  .subprocessor2 = process2
,  .subprocessor3 = process3
,  .strategy1 = special_strategy
};

// Где-то в юнит-тестах
TEST(TestsSuite_01, Test) {
   // Тестовый процессор с моками.
   // Я использовал лямбды, но замокать можно чем угодно
   const Processor test_processor {
      .subprocessor1 = [](/* . . . */) {}
   ,  .subprocessor2 = [](/* . . . */) {}
   ,  .subprocessor3 = [](/* . . . */) {}
   ,  .strategy1 = [](/* . . . */) {}
   ,  .strategy2 = [](/* . . . */) {}
   ,  .strategy3 = [](/* . . . */) {}
   };
   EXPECTED_EQ((/*. . .*/), test_processor(/*. . .*/))
}


Конечно, можно было бы все мемберы класса сделать параметрами шаблона, но тут можно и так, и эдак, у каждого подхода свои плюсы и минусы.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 02.05.2026 13:34 rg45 . Предыдущая версия . Еще …
Отредактировано 02.05.2026 13:34 rg45 . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.