НИУ ВШЭ · Факультет компьютерных наук
Курсовая работа · Исследовательский проект · 2026
MPL · Meta Programming Language

Неявные
интерфейсы на MPL

// Implicit Interfaces in MPL
Автор
Смирнягин Артём Константиновичгруппа БПМИ235, 3 курс
Научный руководитель
Захаров Сергей Алексеевичнаучный сотрудник ФКН НИУ ВШЭ
Неявные интерфейсы на MPL
Предметная область
02 · Предметная область

Интерфейсы: две модели связывания

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

Явное указание

JavaC++C#

Класс обязан явно объявить, какие интерфейсы он реализует. Компилятор проверяет соответствие по имени

class Dog implements Animal { … }
Структурная

По набору методов

GoTypeScript

Тип считается реализующим интерфейс, если предоставляет нужные методы — без явного объявления

type Animal interface { Speak() }
В этой работе реализуем структурную модель без изменения компилятора — средствами самого MPL
Неявные интерфейсы на MPL
Язык MPL
03 · Язык MPL

Meta Programming Language

01

Стек + постфиксная запись

Выражения читаются слева направо, операторы работают со стеком значений

02

Строгая типизация через schemas

Int, Nat, Real, Dict, List, Tuple, Code, Ref и Meta-типы нулевого размера

03

Упор на метапрограммирование

Интроспекция структур, статическая рекурсия, прямое управление памятью

Пример программы

{} () {} [ x: 5; y: @x 3 +; y printStack ] "main" exportFunction

Арсенал для интерфейсов

ucall · статическая рекурсия
callField · интроспекция полей
storageAddress · работа с памятью
virtual · значения нулевого размера
Инфраструктура: компилятор mplc· LLVM-бэкенд· стандартная библиотека mpl-sl· mpl.mindway.org
Неявные интерфейсы на MPL
Актуальность
04 · Актуальность

Почему это важно

01

MPL — редкий случай языка, где расширения пишутся на самом языке

Явные интерфейсы уже реализованы без модификации mplc. Неявные — следующий логичный шаг

02

Go-подобные структурные интерфейсы в средах с метапрограммированием не изучены

В литературе описаны либо «Go изнутри», либо подходы, встроенные в компилятор (Rust-трейты, C++-concepts). Промежуточный путь — через инструменты языка — остаётся белым пятном

03

Снижение связности кода — практическая ценность

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

Неявные интерфейсы на MPL
Цель и задачи
05 · Цель и задачи

Что именно делаем

Цель работы
Разработать и экспериментально проверить модель неявных интерфейсов для MPL — без модификации компилятора

Задачи

  1. Проанализировать подходы к интерфейсам в современных языках программирования
  2. Разобрать текущую явную модель интерфейсов в MPL
  3. Спроектировать и реализовать прототип неявных интерфейсов
  4. Построить тестовую инфраструктуру и серию бенчмарков
  5. Сравнить неявную и явную модели по ключевым метрикам
Неявные интерфейсы на MPL
Аналоги · Go как ориентир
06 · Обзор аналогов

Подходы к интерфейсам и диспетчеризация

Диспетчеризация — как программа выбирает, какой код вызвать по имени метода. Статическая — выбор на этапе компиляции (C++-concepts, Rust-мономорфизация), динамическая — во время выполнения (vtable, itable)
Номинативная

Java · C++

  • Связывание явное (implements)
  • Диспетчеризация динамическая через vtable
Структурная

Go

  • Связывание автоматическое по набору методов
  • Диспетчеризация динамическая через itable
  • Значение = (itable, data) — два машинных слова
Трейты

Rust

  • Связывание явное (impl Trait for Type)
  • Диспетчеризация статическая; dyn Trait — динамическая
Concepts

C++20

  • Связывание структурные требования к шаблонным аргументам
  • Диспетчеризация чисто статическая
Ориентир — GoНужна комбинация «структурная типизация + динамическая диспетчеризация». Именно эту модель воссоздаём в MPL средствами метапрограммирования
Неявные интерфейсы на MPL
Текущая явная модель
07 · Явные интерфейсы в MPL

Устройство interface / implement

Устройство явной модели
Ключевоеinterface формирует vtable и CALL-кодблок. implement связывает интерфейс с конкретным типом. Префикс памяти интерфейсной и реализационной частей совпадает — приведение типа реализации к базовому работает автоматически
Неявные интерфейсы на MPL
Ограничения явной модели
08 · Ограничения

Что мешает явной модели

Это не баги реализации, а архитектурные ограничения
01
Нет множественной реализации

У класса ровно один base из-за совпадения префикса памяти

02
Нет цепочек интерфейсов

После implement объект уже не интерфейс — нельзя построить иерархию «b реализует a, c реализует b»

03
Есть DIE, нет INIT

Интерфейс умеет освобождать память, но не инициализировать объект — не работает напрямую с Array

04
Жёсткая зависимость от Owner

Универсальная обёртка владения с захардкоженным .lower

05
Лишняя обвязка

Постоянные .lower, .init, дополнительные подключения в каждом файле

Чтобы снять эти ограничения — нужна принципиально другая модель
Неявные интерфейсы на MPL
Дизайн · от set/same к .wrap
09 · Дизайн неявных интерфейсов

От «прозрачных» обёрток к явному .wrap

Первая идея · отвергнута

Сделать интерфейсы «прозрачными» — перегрузить встроенные set (побайтовое копирование) и same (проверка типов). Тогда контейнеры сами бы оборачивали объекты

  • Нужно подключать implicitInterface.mpl во всех файлах — это модификация стандартных контейнеров
  • Высокий риск побочных эффектов: нестандартное set, ложные same, трудноуловимые ошибки
Итоговый дизайн — явная обёртка через .wrap
было
some_impl Owner.new .lower interface.new @arr.append
стало
some_impl some_interface.wrap @arr.append
Детали реализации скрыты, но момент обёртки остаётся под контролем пользователя
Неявные интерфейсы на MPL
Внутреннее устройство · механизм .wrap
10 · Внутреннее устройство и механизм

Структура implicitInterface и оживление заглушек

Что лежит внутри обёртки

  • INTERFACE_DATAуказатель на реальные данные
  • INTERFACE_SIZEразмер блока данных
  • method.i_IMPLcodeRef-заглушки с суффиксом _IMPL
  • CALL_IMPL / CALLесли интерфейс Callable
  • DESTRUCT, DIE, INITуправление временем жизни

Как .wrap оживляет заглушки

Заготовка при создании интерфейса

  • Генерируются пустые _IMPL-заглушки
  • Прокси-структуры ведут вызовы на эти заглушки

Оживление при вызове .wrap

  • Заглушки получают тела, приводят INTERFACE_DATA к конкретному типу
  • Вызов через callField — имитация vtable
Неявные интерфейсы на MPL
Внутреннее устройство · схема
11 · Внутреннее устройство

Схема implicitInterface

Структура implicitInterface
Неявные интерфейсы на MPL
Преимущества неявной модели
12 · Преимущества

Что получаем взамен

01
Множественная реализация

Обёртка создаётся внешним кодом, не фиксируется в типе. Один класс можно обернуть в несколько интерфейсов

02
Совместимость с контейнерами

Объекты после .wrap помещаются в Array, HashTable — без Owner, .lower и прочей обвязки

03
Полная развязка

Класс-реализация может не знать о существовании интерфейса — ключевая идея структурной типизации

04
Встроенное владение

Интерфейс содержит собственные INIT, DIE, DESTRUCT. Управление временем жизни — внутри интерфейса

↗ Реализация · github.com/temikgo/implicit-interface
Неявные интерфейсы на MPL
Тестовый проект · RPG
13 · Экспериментальная инфраструктура

RPG-игра как симуляция реальной кодовой базы

Архитектура

главный объект · Dungeonгерой проходит локации, ведёт журнал событий
герой · Heroздоровье, атака, прочность щита, инвентарь зелий
локация · Roomсписок противников и зелий, логика «прохождения»
интерфейс · Mob→ Dragon · Goblin · Skeleton
интерфейс · Potion→ BarrierBrew · HealingPotion · IronheartElixir

Вложенная структура данных

Room
Array<Mob>
Mob
Array<Potion>
Array<Potion>

Почему именно RPG

  • Смешанное использование контейнеров — Array и HashTable
  • Много сущностей и реализаций, чёткие границы ответственности
  • Взаимодействия идут через интерфейсы, а не через конкретные типы
  • Простое масштабирование — число локаций, противников, зелий, методов, реализаций
Неявные интерфейсы на MPL
Планирование эксперимента · методика
14 · Критерии и методика

Серии бенчмарков × 6 метрик

Метрики

ms
Время компиляции
ms
Время выполнения
kb
LLVM-IR (.ll)
kb
Бинарный файл
mb
Память выполнения
mb
Память компиляции

Серии

01 · Sweep по миру
Число локаций / противников / зелий

Типичное расширение проекта без изменения структуры

02 · Размер интерфейса
Число методов интерфейса Potion

Дубликаты getName, нигде не вызываются

03 · Число реализаций
Копии BarrierBrew

Разные имена одной и той же реализации

Как замеряли

  • 5 прогонов на точку, берётся медиана
  • Время — time.perf_counter(), монотонный wall clock
  • Размеры — os.path.getsize для .ll и бинарника
  • Каждый прогон — в чистой временной директории
  • Пайплайн mplc → .ll → clang++ → bin
Неявные интерфейсы на MPL
Бенчмарк 1 · R sweep
15 · Бенчмарк 1 — расширение мира

Рост числа локаций (R)

R sweep — 6 метрик
X — число локаций (R) Y — ms / kb / mb по метрикам explicit implicit
ВыводМодели сопоставимы, лёгкий проигрыш implicit по памяти компиляции
Неявные интерфейсы на MPL
Бенчмарк 1 · M sweep
16 · Бенчмарк 1 — расширение мира

Рост числа противников (M)

M sweep — 6 метрик
X — число противников (M) Y — ms / kb / mb по метрикам explicit implicit
ВыводМодели сопоставимы — та же картина, что и на R sweep
Неявные интерфейсы на MPL
Бенчмарк 1 · P sweep
17 · Бенчмарк 1 — расширение мира

Рост числа зелий (P)

P sweep — 6 метрик
X — число зелий (P) Y — ms / kb / mb по метрикам explicit implicit
Итог бенчмарка 1На естественном росте мира implicit и explicit практически неотличимы
Неявные интерфейсы на MPL
Бенчмарк 2 · методы интерфейса
18 · Бенчмарк 2 — размер интерфейса

Слабое место: рост числа методов одного интерфейса

Methods sweep — 6 метрик
X — число методов Potion Y — ms / kb / mb explicit implicit
Runtime
×6
Compile
×5
Размеры
×3
Память
×9
Неявные интерфейсы на MPL
Бенчмарк 3 · число реализаций
19 · Бенчмарк 3 — реализации

Рост числа реализаций одного интерфейса

Implementations sweep — 6 метрик
X — число реализаций Potion Y — ms / kb / mb explicit implicit
  • Runtime и память выполнения — почти равны
  • .ll и бинарный файл — выигрывает implicit
  • Компиляция — explicit чуть быстрее, но ест больше памяти
ВыводНеявная модель выигрывает по размеру артефактов и не уступает по времени выполнения при росте числа реализаций
Неявные интерфейсы на MPL
Снижение связности · пример
20 · Снижение связности на уровне файла

Dragon реализует Mob — было и стало

Явная модель
Dragon в явной модели
7 use-импортов · implement · Owner-обёртка массива
Неявная модель
Dragon в неявной модели
4 use-импорта · без implement · прямой Potion Array
Итог на файлеУбрали 3 импорта (Owner, interface, interfaces/Mob), сняли обвязку implement и Owner Array — то же поведение, но файл больше не зависит от инфраструктуры интерфейсов
Неявные интерфейсы на MPL
Основные результаты
20 · Основные результаты

Что сделано и что выяснили

Что сделано

Реализована модель неявных интерфейсов для MPL без модификации компилятора
Построена экспериментальная инфраструктура: 4 серии бенчмарков × 6 метрик + анализ графа импортов
Получено корректное сравнение на нетривиальной кодовой базе (RPG-игра)
Плюсы неявной модели
  • Развязка модулей и множественная реализация
  • Прямая совместимость с контейнерами (Array, HashTable)
  • Меньше связности: граф импортов implicit 24 / 86 против explicit 25 / 107 — у неявной модели и меньше модулей, и на 20% меньше зависимостей между ними
Минусы
  • Сильная деградация при росте числа методов одного интерфейса (×5–9)
  • Больше памяти при компиляции
Практический вывод: неявная модель — реалистичная альтернатива явной в сценариях с небольшими интерфейсами и множеством реализаций
Неявные интерфейсы на MPL
Дальнейшая работа и источники
21 · Finale

Что дальше

Дальнейшая работа

  • Оптимизация генерации _IMPL — сократить деградацию по числу методов
  • Применить неявные интерфейсы в стандартной библиотеке, продуктовых и инфраструктурных проектах — ускорить, упростить код и реализовать то, что раньше было невозможно
  • IDE-поддержка: автопоиск типов, удовлетворяющих интерфейсу

Репозитории

Ключевые источники

Russ Cox · Go Data Structures: Interfaces
Steve Klabnik et al. · The Rust Programming Language
B. Stroustrup · A Tour of C++ (2nd ed.)
E. Gamma et al. · Design Patterns
M. Fowler · Patterns of Enterprise Application Architecture
· полный список (13) в отчёте ·
Спасибо за внимание
Неявные интерфейсы на MPL
Граф импортов · явная модель
Extra · граф импортов

Связность: явная модель

Вершины — модули, рёбра — импорт одного модуля другим. 25 вершин · 107 рёбер
Граф импортов явной модели
Неявные интерфейсы на MPL
Граф импортов · неявная модель
Extra · граф импортов

Связность: неявная модель

Вершины — модули, рёбра — импорт одного модуля другим. 24 вершины · 86 рёбер
Граф импортов неявной модели
Неявные интерфейсы на MPL
Сводный бенчмарк · R sweep
Extra · сводный бенчмарк

R sweep — explicit · implicit · sharedV1/V2/V3

Все 6 метрик, 5 вариантов реализации на одной сетке. Ось X — число локаций (R)
R sweep — все варианты
Неявные интерфейсы на MPL
Сводный бенчмарк · M sweep
Extra · сводный бенчмарк

M sweep — explicit · implicit · sharedV1/V2/V3

Все 6 метрик, 5 вариантов реализации. Ось X — число противников (M)
M sweep — все варианты
Неявные интерфейсы на MPL
Сводный бенчмарк · P sweep
Extra · сводный бенчмарк

P sweep — explicit · implicit · sharedV1/V2/V3

Все 6 метрик, 5 вариантов реализации. Ось X — число зелий (P)
P sweep — все варианты
Неявные интерфейсы на MPL
Сводный бенчмарк · методы
Extra · сводный бенчмарк

methods sweep — explicit · implicit · sharedV1/V2/V3

Все 6 метрик, 5 вариантов реализации. Ось X — число методов интерфейса Potion
methods sweep — все варианты
Неявные интерфейсы на MPL
Сводный бенчмарк · реализации
Extra · сводный бенчмарк

implementations sweep — explicit · implicit · sharedV1/V2/V3

Все 6 метрик, 5 вариантов реализации. Ось X — число реализаций Potion
implementations sweep — все варианты
← → space · F11 fullscreen · Home/End