RFC-019: Типовая гомоиконичность (Typed Homoiconicity) - Синтаксис как тип
⚠️ Постоянное экспериментальное уведомление: Это исследовательский эксперимент, направленный на проверку осуществимости языковой конструкции "синтаксис как тип". Данный RFC никогда не будет слит, независимо от результатов он не попадёт в ветку dev/main. Экспериментальная ветка будет удалена или архивирована после завершения.
- Цель эксперимента: Оценить сложность реализации и потенциальную ценность типовой гомоиконичности
- Стоп-сигнал: Отказ при отсутствии прогресса в течение 6 месяцев
- Критерий успеха: Возможность запустить хотя бы одно пользовательское ключевое слово (полный цикл: парсинг → компиляция → выполнение)
Не гарантируется слияние в main ветку, в будущем RFC может быть отклонён или заброшен по различным причинам. Не используйте эту функциональность в продакшене.
⚠️ Пояснение к позиционированию: Данный RFC представляет собой мысленный эксперимент в области дизайна языка, не предоставляя инженерного решения. Для практического расширяемого режима парсинга см. Rust
syn::Parseили Haskellparsec.
Аннотация
Данный RFC предлагает радикальный эксперимент в дизайне языка: сделать синтаксическую структуру языка частью системы типов.
Ключевая идея заимствована из Lisp — "код как данные" (гомоиконичность), но реализована через статическую систему типов:
- Синтаксическое дерево (AST) является типом
- Ключевые слова — это предопределённые экземпляры типов
- Пользователи могут расширять синтаксис языка через определение типов
Это означает: язык сам становится набором composable, расширяемых "строительных блоков".
Мотивация
Зачем нужен этот эксперимент?
- Стремление к унификации: Устранить "ключевые слова" как особый синтаксический элемент, сделать всё типами и функциями
- Расширяемость языка: Пользователи могут определять новые синтаксические конструкции так же, как определяют функции
- Типобезопасные макросы: Традиционные макросы (текстовая подстановка) опасны, типовая гомоиконичность обеспечивает проверку на этапе компиляции
- Образовательные цели: Глубокое понимание сущности дизайна языков
Связь с Lisp
Lisp давно реализует "код как данные":
; Код на Lisp сам по себе является S-expression
(if (> x 0) "positive" "negative")Отличие данного эксперимента: статическая система типов усиливает эту идею.
Предложение
Ключевые концепции
1. Синтаксическое дерево как тип (AST as Type)
// Узлы синтаксического дерева являются типами
If: Type = { condition: Expr, then: Block, else: Block }
While: Type = { condition: Expr, body: Block }
Return: Type = { value: Expr }
Block: Type = { statements: Array[Expr] }
Let: Type = { name: String, value: Expr, body: Expr }
Function: Type = { params: Array[Param], body: Expr }
Call: Type = { func: Expr, args: Array[Expr] }
// Базовые типы
Literal: Type = { value: Int }
StringLiteral: Type = { value: String }
Variable: Type = { name: String }2. Ключевые слова = функции, обрабатывающие типы
//Evaluator — это функции, обрабатывающие эти типы
eval_if: (node: If, env: Env) -> Value = ...
eval_while: (node: While, env: Env) -> Value = ...
eval_return: (node: Return, env: Env) -> Value = ...
eval_block: (node: Block, env: Env) -> Value = ...
// Компилятор тоже может быть функцией
compile_if: (node: If, ctx: CompileContext) -> IR = ...
compile_while: (node: While, ctx: CompileContext) -> IR = ...3. Типы несут правила парсинга (ключевая инновация)
Это ключевой момент эксперимента: тип не только описывает данные, но и несёт правила разбора кода.
// Тип синтаксического правила
SyntaxRule: Type = {
// Как разобрать код этого типа
parse: (token_stream: TokenStream) -> (Self, remaining_tokens)
// Как скомпилировать/вычислить экземпляр типа
compile: (node: Self, ctx: CompileContext) -> IR
eval: (node: Self, env: Env) -> Value
}
// Синтаксическое правило для типа IF
IF: SyntaxRule = {
// Разбор "if (cond) { then } else { else }"
parse: (tokens: TokenStream) -> (If, remaining) = {
consume("if")
cond = parse_expression(tokens)
consume("{")
then_block = parse_block(tokens)
consume("}")
consume("else")
consume("{")
else_block = parse_block(tokens)
consume("}")
return If(cond, then_block, else_block), tokens
}
eval: (node: If, env: Env) -> Value = {
if eval(node.condition, env) != 0 {
return eval(node.then, env)
} else {
return eval(node.else, env)
}
}
}4. Расширение синтаксиса пользователем
Пользователи могут определять собственные "ключевые слова":
// Пользователь определяет новую синтаксическую конструкцию: unless
Unless: SyntaxRule = {
parse: (tokens: TokenStream) -> (If, remaining) = {
consume("unless")
cond = parse_expression(tokens)
consume("{")
body = parse_block(tokens)
consume("}")
// unless эквивалентен if (!cond) { body }
return If(Not(cond), body, Block([])), tokens
}
}
// Использование
unless x > 0 {
print("x is not positive")
}
// Раскрывается в
if !(x > 0) {
print("x is not positive")
}Примеры
Полный пример: пользовательский синтаксис цикла
// Определяем цикл "times": n.times { ... } выполняется n раз
TimesLoop: SyntaxRule = {
parse: (tokens: TokenStream) -> (While, remaining) = {
receiver = parse_expression(tokens) // Получаем число
consume(".times")
consume("{")
body = parse_block(tokens)
consume("}")
// Преобразуем в while-цикл
counter_var = gensym("i")
return While(
Less(Variable(counter_var), receiver),
Block([
body,
Assign(counter_var, Add(Variable(counter_var), Literal(1)))
])
), tokens
}
}
// Использование
5.times {
print("Hello!")
}
// Раскрывается в
i = 0
while i < 5 {
print("Hello!")
i = i + 1
}Пример: Синтаксис сопоставления с образцом
// Пользователь определяет сопоставление с образцом
Match: SyntaxRule = {
parse: (tokens: TokenStream) -> (MatchNode, remaining) = {
subject = parse_expression(tokens)
consume("{")
cases = []
while !check("}") {
pattern = parse_pattern(tokens)
consume("=>")
body = parse_expression(tokens)
cases.push((pattern, body))
}
consume("}")
return MatchNode(subject, cases), tokens
}
}
// Использование
match x {
0 => "zero",
1 => "one",
n if n > 10 => "big",
_ => "other"
}Детальный дизайн
Архитектура системы
┌─────────────────────────────────────────────────────┐
│ Исходный код │
└─────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Синтаксический анализатор (Parser) │
│ - Распознавание ключевых слов │
│ - Поиск соответствующего типа SyntaxRule │
│ - Вызов метода parse типа │
└─────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ AST (экземпляры типов) │
│ If, While, Match, TimesLoop... │
└─────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Компилятор/Интерпретатор │
│ - Вызов методов compile/eval типа │
│ - Генерация целевого кода или выполнение │
└─────────────────────────────────────────────────────┘Ключевые технические проблемы
1. Функционализация потока управления
Проблема: if должен вычислять только одну ветку, нельзя использовать обычный вызов функции.
Решение: Передавать thunk (отложенное вычисление)
// Внутреннее представление после компиляции
If: Type = {
condition: Expr,
then: () -> Value, // thunk, отложенное вычисление
else: () -> Value
}2. Нелокальный возврат return
Проблема: return должен выходить из нескольких уровней функций.
Решения:
- Вариант A: CPS-преобразование на этапе компиляции
- Вариант B: Использование Result/Either монады
- Вариант C: Ограничение области видимости return
3. Синтаксическая неоднозначность
Проблема: Как различить if(x > 0) { 1 } — вызов функции или ключевое слово?
Решения:
- Ключевые слова используют специальный синтаксис (например,
if ... { } else { }) - Или ограничение через систему типов
4. Бесконечная рекурсия
Проблема: Пользователь может определить самореферентные синтаксические правила.
Решение: Обнаружение циклических зависимостей на этапе компиляции
Связь с существующими системами
Связь с RFC-010 (Унифицированный синтаксис типов)
RFC-010 реализовал унифицированный синтаксис name: type = value, данный RFC является его развитием:
| RFC-010 | Данный RFC |
|---|---|
Переменные, функции, типы — всё name: type = value | Ключевые слова тоже name: type = value |
| Типы являются значениями | Синтаксические правила являются значениями |
Type — метатип | SyntaxRule — метатип синтаксиса |
Сравнение с Lisp/макросами
| Характеристика | Макросы Lisp | Данный эксперимент |
|---|---|---|
| Представление кода | S-expression (списки) | Экземпляры типов |
| Способ расширения | defmacro | Определение типа SyntaxRule |
| Типобезопасность | Слабая (текстовая подстановка) | Сильная (проверка типов) |
| Время парсинга | runtime/compile-time | Этап компиляции |
| Поддержка IDE | Слабая | Сильная (информация о типах) |
План работы с ветками
Экспериментальная ветка
Имя ветки: exp/typed-homoiconicity
Создаётся от ветки devВажно:
- Это экспериментальная ветка, не будет часто сливаться с dev
- Может долгое время разрабатываться независимо
- Не гарантируется слияние в main
- В случае неудачи эксперимента ветка будет удалена
Этапы разработки
⚠️ Верхний временной лимит эксперимента: 6 месяцев
| Фаза | Цель | Ожидаемое время | Примечания |
|---|---|---|---|
| Phase 1 | Proof of concept: AST-типы на существующем синтаксисе | 2 недели | |
| Phase 2 | Реализация базового evaluator | 2 недели | Ключевая проблема: управление потоком if/return |
| Phase 3 | Реализация правил парсинга для типа SyntaxRule | 3 недели | |
| Phase 4 | Расширение синтаксиса пользователем | 3 недели | Основная цель: запустить хотя бы одно пользовательское ключевое слово |
| Phase 5 | Оптимизация и документация | 2 недели | Завершение эксперимента |
Обработка таймаута: Если Phase 2 (реализация управления потоком) не показывает прогресса более 4 недель, следует рассмотреть отказ.
Компромиссы
Преимущества
- Предельная унификация: Устранение границы между ключевыми словами и обычным кодом
- Расширяемость языка: Пользователи могут определять собственный синтаксис
- Типобезопасность: Безопаснее традиционных макросов
- Образовательная ценность: Глубокое понимание сущности языков
Недостатки
- Сложность реализации: Требуется значительная переработка компилятора
- Опасения по производительности: Интерпретация на runtime может быть медленной
- Крутая кривая обучения: Абстрактные концепции, требуется понимание системы типов
- Сомнительная практичность: Возможно избыточная инженерия
Риски
- Эксперимент может провалиться, не найдя практического применения
- Сложность реализации может превысить ожидания
- Конфликт с существующими возможностями
Открытые вопросы
- [ ] Как обрабатывать синтаксические конфликты (конфликт пользовательских правил со встроенными)?
- [ ] Планы оптимизации производительности?
- [ ] Нужен ли механизм импорта/экспорта синтаксиса?
- [ ] Как интегрировать с существующей модульной системой?
Приложение
Глоссарий терминов
| Термин | Определение |
|---|---|
| Гомоиконичность (Homoiconicity) | Код и данные используют одинаковое представление |
| AST (Синтаксическое дерево) | Абстрактное синтаксическое представление программы |
| SyntaxRule | Тип, несущий правила синтаксического разбора |
| Thunk | Обёртка функции для отложенного вычисления |
| CPS | Continuation Passing Style, стиль передачи продолжений |
Библиография
Жизненный цикл и судьба
┌─────────────┐
│ Черновик │ ← Текущее состояние
└──────┬──────┘
│
▼
⚠️ Постоянная экспериментальная ветка (exp/typed-homoiconicity)
Возможные результаты:
├─► Успешная проверка → Архивировать, никогда не сливать
├─► Неудача → Удалить ветку
└─► Таймаут → Отказ и удаление
⚠️ При любом исходе данный RFC никогда не будет слит⚠️ Важное напоминание: Это исследовательский эксперимент, никогда не будет слит. Не полагайтесь на эту функциональность в продакшен-коде.
