Сколько парадигм программирования вы сможете назвать? Список на этой странице википедии содержит ни много ни мало 76 наименований. Этот список может быть пополнен ещё одним подходом, названным Design by Introspection. Его основная идея состоит в активном использовании простых средств метапрограммирования и интроспекции типов (времени компиляции) для создания эластичных компонентов. Автором данного подхода является Андрей Александреску. В статье были использованы материалы из его выступления на DСonf 2017. В 2001 году в книге «Современное проектирование на С++» был представлен паттерн под названием policy-based design. В целом, это паттерн «стратегия», но использующий шаблоны и собирающийся во время компиляции. Шаблонный класс host принимает своими параметрами набор типов policy, реализующих каждый какую-то независимую функциональность, и внутри себя использует интерфейсы, предоставляемые этими компонентами: Здесь шаблон описан коротким синтаксисом. (T, Prod, Error) — его параметры.
Инстанцирование выглядит так: Плюсы очевидны: эффективность шаблонов, хорошее разделение и повторное использование кода. Однако компоненты являются цельными, неразборными. Если отсутствуют какая-то часть интерфейса, то это приведёт к ошибке компиляции. Попробуем развить эту схему, придать компонентам «пластичность». Итак, для этого нам понадобится: Давайте взглянем, какие средства языка D можно использовать по каждому из пунктов: Важнейшим свойством Design by Introspection являются опциональные интерфейсы. Здесь компонент содержит R обязательных примитивов (может быть 0) и O необязательных. С помощью интроспекции можно выяснить, задан ли определённый примитив, причём знание о пропущенных примитивах так же важно, как и о тех, которые компонент содержит. Число возможных интерфейсов, таким образом, становится равным 2O. static if – простое, но мощное средство, делающее «магический форк», удваивая число вариантов использования кода. Оно позволяет писать линейный код с экспоненциальным ростом количества возможных поведений. Экспоненциального же роста генерируемого компилятором кода не происходит: вы заплатите только за те инстансы шаблонов, которые действительно используете в своём приложении. В качестве примера использования DbI рассмотрим std.experimental.checkedint – модуль стандартной библиотеки Phobos, реализующий безопасную работу с целыми числами. Какие операции с машинными целыми являются небезопасными? Можно честно вставлять проверки после каждой операции, а можно разработать тип, который бы это делал за нас. При этом возникает множество вопросов: Создадим «оболочку», принимающую шаблонными параметрами базовый тип и «хук», который будет осуществлять наши проверки: У хука далеко не всегда есть состояние. Давайте это учтём, использовав static if: Здесь нам на руку то, что в синтаксисе языка D точка используется и для обращения к полям объекта непосредственно, и через указатель, и к его статическим членам.
Настроим ещё и значение по умолчанию. Это может быть полезно для хуков, определяющих некоторое NaN значение. Здесь мы используем шаблон hasMember: В качестве примера того, как много поведений может содержать небольшой фрагмент кода, приведу перегруженные операторы инкремента и декремента. Если хук перехватывает эти операции, делегируем их ему: В противном случае, обработаем переполнение: Наконец, если ничего не было перехвачено, применяем операцию как обычно: Этот строковый миксин развернётся в ++payload; или --payload; в зависимости от операции. Традиционно, отсутствие какой-то части интерфейса приводит к ошибке. Здесь же это приводит к отсутствию части возможностей: В модуле std.experimental.checkedint определено несколько стандартных хуков: Хук может содержать: А написание собственного займёт меньше 50 строчек кода. Для примера, запретим все сравнения знаковых чисел с беззнаковыми: До этого Checked принимал основным параметром только базовые типы. Обеспечим композицию, позволим ему принимать другой Checked: Это открывает интересные возможности: а также вносит бессмысленные комбинации: и просто странные: Для решения этого вопроса предлагается использовать «полуавтоматическую» композицию: С помощью alias мы выбрали статические методы из существующих хуков и сделали из них свой, новый хук. Вот так мы можем расположить атомы как нам угодно! Рассмотренный подход существует во многом благодаря static if. Этот оператор расширяет пространство вариантов использования кода. При масштабировании Design by Introspection потребует некоторой поддержки со стороны инструментов разработчика.