Теория языков программирования

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

Хорошие программисты пишут код. Великие программисты пишут код, который пишет код. Каждый раз при использовании @Autowired, @derive(Debug) или protobuf - метапрограммирование уже работает за разработчика.

  • **NestJS / Spring**: IoC-контейнеры используют рефлексию для Dependency Injection - миллионы строк enterprise кода существуют благодаря этому паттерну
  • **Rust derive macros**: #[derive(Serialize, Deserialize)] генерирует весь serde код - без этого экосистема Rust была бы намного сложнее
  • **Protocol Buffers / gRPC**: Google генерирует клиент-серверный код для 50+ языков из одной .proto схемы - основа внутренней инфраструктуры Google
  • **Webpack / Babel**: AST-трансформации превращают современный JS в совместимый - это макросы на уровне инфраструктуры фронтенда

Макросы

Макросы трансформируют синтаксическое дерево (AST) во время компиляции. Это позволяет расширять язык без изменения компилятора. Lisp-макросы работают с homoiconicity - код и данные одинаковы (списки). Rust proc-macros работают с TokenStream.

Почему unless нельзя реализовать как обычную функцию в большинстве языков?

Рефлексия

Рефлексия - способность программы исследовать и модифицировать собственную структуру в runtime. Introspection (читать) vs Intercession (изменять). Используется в IoC-контейнерах, сериализации, ORM, тестовых фреймворках.

В чём основной недостаток рефлексии по сравнению с compile-time метапрограммированием?

Кодогенерация

Кодогенерация - создание исходного кода или байткода программой. Бывает внешней (protobuf генерирует .java/.py из .proto) и внутренней (JVM JIT генерирует машинный код из байткода). Template metaprogramming в C++ - compile-time кодогенерация.

Какое главное преимущество кодогенерации из схемы (как protobuf) перед ручным написанием serialization кода?

Multi-stage Programming

Staging - разделение вычислений на фазы. Статически известные значения вычисляются на ранней стадии, динамические - на поздней. Это позволяет специализировать общий код под конкретные входные данные, получая производительность ad-hoc кода с гибкостью общего.

Метапрограммирование всегда делает код сложнее и хуже читаемым

Хорошее метапрограммирование устраняет boilerplate и делает код декларативнее. Плохое - магия без понимания

#[derive(Serialize)] в Rust читается лучше, чем 50 строк ручного impl. Проблема не в метапрограммировании, а в его злоупотреблении

Какова ключевая идея multi-stage programming?

Ключевые идеи

  • **Макросы** трансформируют AST до выполнения. Lisp homoiconicity делает код = данные. Rust proc-macros - типобезопасные AST трансформации
  • **Рефлексия** - runtime introspection. Удобно для IoC/DI, но ценой compile-time безопасности и производительности
  • **Кодогенерация** - единый источник истины. Protobuf: одна схема, любое количество языков, гарантированная совместимость
  • **Staging** - специализация кода по статически известным данным. power(x, 8) -> unrolled multiplication без runtime overhead

Связанные темы

Метапрограммирование пронизывает компиляцию и DSL:

  • Макросы подробно — Детальное рассмотрение C-макросов, Lisp-макросов, гигиены и proc-macros
  • DSL компиляторы — Staging и кодогенерация - основа создания DSL компиляторов

Вопросы для размышления

  • Homoiconicity в Lisp означает, что код - это данные. Как это принципиально меняет возможности метапрограммирования по сравнению с Rust proc-macros?
  • Reflection обходит систему типов. В Spring Boot это позволяет DI без boilerplate. Как найти баланс между удобством и type safety?
  • Template Haskell позволяет проверять SQL запросы во время компиляции Haskell. Почему эта возможность не стала мейнстримом, несмотря на очевидную пользу?

Связанные уроки

  • comp-01-intro
Метапрограммирование

0

1

Войти