Обновить

Свободные функции вместо методов, но с полиморфизмом. Что это даёт на самом деле?

Уровень сложностиПростой
Время на прочтение8 мин
Охват и читатели6.7K
Всего голосов 10: ↑10 и ↓0+13
Комментарии13

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

В СССР это направление называлось эволюционной разработкой. Были работы:
Фуксман А.Л. Технологические аспекты создания программных систем.
Горбунов-Посадов, М. М. Расширяемые программы.

Процедурно-параметрический подход решает многие проблемы, представленные в Expression Problem.

Всё это, конечно, делается и на голом C++: unordered_map с ключом из пары type_index и методами для регистрации. Локализация правок будет та же. Только компилятор не будет проверять покрытие всех пар, конкретные типы придётся привести к базовому и в каждом обработчике кастовать обратно вручную — компилятор эти касты также не проверяет. В ППП за этим следит компилятор.

На C++ можно тоже через таблицу реализовать, а с шаблонами не надо в обработчиках ничего кастовать. Проверить таблицу можно во время запуска приложения (перед main), например. А как в вашем ППП-компиляторе реализована проверка?

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

Реализация проверок осуществляется во время компиляции. Пока таблица собирается целиком и проверяется на пропущенные пары при запуске, тоже перед main.

Взаимоисключающие утверждения.

В планах перенести сборку таблицы в свой компоновщик.

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

Взаимоисключающие утверждения

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

Спасибо за обсуждение. Технических деталей правда мало, так как статья изначально планировалась обзорной. Если будет интерес к теме со стороны читателей, можно будет написать статью с разбором технических деталей и осветить все вопросы, которые всплывут в комментариях к этой статье. В том числе подробнее сравненить компилятор с C++ библиотекой. Если есть интерес прямо сейчас - можно почитать здесь.

То есть это "процедурно‑параметрическое программирование" - по сути мультиметоды с динамической диспетчеризацией, то есть свободные функции + какой-то аналог таблицы виртуальных функций?

И интересно как это устроено на низком уровне, то есть те самые таблицы. Ведь если в C++ у нас указатель на vbable хранится в начале объекта, то здесь несколько объектов...

Именно. Матчасть с примерами даже в вике

Лучше конечно в английской вике.

Изобретено в 1995г, под большинство языков программирования есть библиотеки.

Впрочем для геймдева, для которого приводятся примеры, применимо с оговорками невысокой производительности.

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

Если вопрос в конце не риторический, то вот пример достаточно большой системы: MyCompany. Она написана на lsFusion, где методов, собственно, тоже нет. Свойства и действия глобальные, а реализация может выбираться по классам всех параметров.

Ваш пример с атакой выглядел бы примерно так:

CLASS ABSTRACT Unit;
CLASS Warrior : Unit;
CLASS Mage : Unit;
CLASS Worker : Unit;

attack ABSTRACT MULTI (Unit, Unit);

attack(Warrior attacker, Worker target) + {
    // Warrior attacks Worker
}

attack(Mage attacker, Warrior target) + {
    // Mage attacks Warrior
}

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


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

Минус ровно тот, который вы описали: нельзя открыть класс и увидеть всё, что он умеет. Но это решается поиском реализаций и использований в IDE.

А вот тезис "новый тип ничего не ломает" спорный. Если для него подходит базовая реализация, то да. Если нужна отдельная специализация, а разработчик её забыл, всё может прекрасно скомпилироваться и работать неправильно. В lsFusion для абстрактного действия можно потребовать полное покрытие классов. Без такого контроля проблема никуда не исчезает, просто проявляется в другом месте.

Языков, в которых есть мультиметоды, сейчас хватает. Начиная с CLOS. В основном доступ к параметрам реализуется с использованием хеширования, что медленнее чем в ЗЗС. Сравнение и обзор есть в статье: Сравнение производительности для различных способов реализации мультиметодов. Системы анализа и обработки данных. – 2025. – № 4 (100). – С. 69–84.(https://journals.nstu.ru/vestnik/catalogue/contents/view_article?id=41742).

На уровне PPC полиморфные функции могут контролироваться примерно также как при использовании абстрактных классов в C++. Обобщенная функция может быть абстрактной. Тогда все альтернативы должны быть обязательно реализованы. Если обобщенная функция имеет тело, то она, как и виртуальные методы, используется по умолчанию там, где нет ее замены.

Хотелось бы увидеть реакцию бренч предиктора в процессоре на это

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

Если у тебя цикл по зайчикам и белочкам в перемешку - будет вероятность предсказать переход 50%, как ты там не делай.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации