Liza: модель языка, draft 0.1
Статус: рабочее предложение. Описанные ниже правила для состояния, владения и задач ещё не реализованы и не считаются окончательно утверждённой спецификацией. Реализованное ядро описано в спецификации.
Направление
Liza должна выражать переносимые вычисления и ограничения, а не имитировать конкретный CPU. Выход по умолчанию — нативная программа. Собственный Semantic IR сохраняет логические типы, зависимости, права доступа и эффекты до понижения в машинные операции. Отсутствие указателей в исходном языке не означает, что backend не вправе использовать адреса.
Основные ориентиры: лаконичность, отсутствие обязательного GC, явность дорогостоящих операций, модульный runtime и сложные серверные системы как инженерный критерий. Максимальная эффективность на любом железе — цель, а не универсальная гарантия компилятора.
Сущности и предлагаемые инварианты
| Сущность | Семантика | Основной инвариант |
|---|---|---|
| Value | Неизменяемые данные без наблюдаемой идентичности | Равенство определяется содержимым; физическое хранение не наблюдаемо |
| Transform | Вычисление значений из аргументов | При одинаковых входах одинаковый результат или определённый отказ |
| State | Изменяемая сущность с идентичностью и владельцем | Изменение требует исключительного права доступа |
| View | Ограниченный доступ к части данных без владения | Не переживает владельца; границы проверяются |
| Handle | Типизированное право обращения к ресурсу | Не является адресом; недействительность имеет определённый результат |
| Task | Логическое выполнение в области родителя | Дочернее выполнение не переживает область без явной передачи владения |
| Effect | Наблюдаемое действие или ограничиваемая стоимость | Контракт вызывающего допускает эффекты вызываемого |
Объект-значение не имеет собственной идентичности. Идентичность вводится через State или Handle. Это рабочая гипотеза; её ещё нужно проверить на коллекциях и серверных сценариях.
Значения и локальные привязки
Математическое значение, биты и кодирование
Различаем три уровня:
| Уровень | Что определяет |
|---|---|
| Значение | Математический смысл и допустимые операции типа |
| Битовое представление | N бит и их интерпретацию; для знаковых целых — дополнение до двух |
| Внешнее кодирование | Последовательность байтов протокола или файла с явными порядком байтов и форматом |
Один математический результат не обязан занимать ячейку памяти: реализация может держать его в регистре, вычислить заранее или устранить хранение. N-битное представление для побитовых операций не обещает конкретный layout в памяти. Layout, выравнивание, padding и ABI задаются отдельными контрактами.
Числовое преобразование сохраняет значение либо применяет явно выбранную
политику сужения. Переинтерпретация битов, если появится, будет отдельной
операцией. Сериализация также не должна неявно копировать host-представление:
например, значение u16 0x1234 кодируется байтами 0x12, 0x34 в big-endian
и 0x34, 0x12 в little-endian независимо от устройства хоста.
Это проектная граница для будущих протоколов, FFI и памяти, а не уже доступный API сериализации. Строки, составные значения и zero-copy потребуют собственных правил кодирования и времени жизни представлений.
Логическое копирование и обновление
Для составных копируемых значений принимаем логическую семантику:
b = a связывает b с тем же содержимым, не создавая неявного права изменять
a через b. Компилятор может разделять физическое хранение или исключать
копирование, если это сохраняет наблюдаемое поведение и контракт стоимости.
Будущий var разрешает заменить значение локальной привязки. Это не вводит
изменяемый объект с идентичностью, изменяемую ссылку или разделяемое состояние.
Обновление поля составного значения ещё нужно определить отдельно;
синтаксис доступа к полю сам по себе не задаёт права мутации объекта.
До реализации массивов требуется контракт для трёх разных действий: логического копирования значения, временного представления данных без владения и передачи владения. Для больших буферов пока не выбраны правила допустимых неявных копий и ограничения стоимости. Скрытые аллокации или copy-on-write не считаются решением по умолчанию.
Варианты, Option и Result должны представляться без обязательной скрытой
аллокации на сам вариант. Стоимость содержащихся в нём данных остаётся явной.
Liza IR описывает эти действия до выбора физического представления. Использование
alloca, load, store или getelementptr в LLVM backend не превращает
значения исходного языка в наблюдаемые адресуемые объекты.
Владение и память: проект правил
- Ресурс имеет одного владельца.
moveпередаёт владение, старое имя после передачи недоступно. Освобождение происходит ровно один раз. - Допустимы несколько read-only views либо один исключительный mutable view. Во время mutable view другие пути доступа к той же области запрещены.
- View не может пережить ресурс. Передача view в task допустима только при доказанном вложенном времени жизни; через suspend потребуется отдельное правило.
shareсоздаёт явно разделяемое неизменяемое владение. Подсчёт ссылок, его атомарность и стоимость не должны скрываться от ограничений медиатракта.- Границы views и недействительные handles должны проверяться. Никакого UB из-за устаревшего handle; механизм поколений и восстановления ошибок ещё открыт.
- Разделяемая мутация возможна только через явно синхронизированные API. Порядки атомарных операций и memory model нужно специфицировать отдельно.
Эти инварианты не равны готовому borrow checker. Нужны правила вывода, примеры допустимых/недопустимых программ и проверка возможности их реализации.
Эффекты, задачи, распределённость
Систему модулей проектируем отдельно от платформенного I/O. До появления консольных и файловых операций определяем эффекты, ошибки ресурсов и порядок их освобождения. Модуль с чистыми преобразованиями не должен автоматически подключать платформенные возможности. Полная проверка эффектов развивается на системном этапе после Core.
Предполагаемые категории: state, alloc, block, io, suspend, remote. Набор
эффектов распространяется по графу вызовов. pure исключает внешние эффекты,
но не доказывает завершение. Отказ арифметики в текущем ядре завершает программу;
нельзя удалять потенциально падающую операцию лишь потому, что её значение не нужно.
realtime в будущем ограничивает операции, а не гарантирует время исполнения
на произвольной ОС. Нужны бюджет, ограничения размера данных и политика полной
очереди. Локальный вызов и RPC не взаимозаменяемы: remote требует явных ошибок,
тайм-аута и отмены. Репликация и консенсус принадлежат библиотекам, а язык должен
помогать проверять их контракты.
Для Task ещё нужно определить отмену, join, распространение ошибок, порядок освобождения ресурсов и гарантии прогресса. Планировщик и reactor подключаются только использующим их программам.
Выходные артефакты
Сейчас: диагностируемый исходник, JSON Liza IR, LLVM IR и host executable. В дальнейшем: object, static/shared library, assembly, WASM и cross-target build. Формат IR прототипа нестабилен; это ещё не переносимый долговременный ABI.
Открытые решения
- Какие отдельные операции арифметики должны возвращать
Result? Текущая checked-арифметика сохраняет trap; её контракт не меняется неявно при расширении типов. Остальные решения — в числовой модели. - Какие большие значения допускают неявное копирование, как отражать его стоимость?
- Какие формы владения выводятся без аннотаций? Что происходит через suspend?
- Может ли pure выделять память, и как отличать логический эффект от стоимости?
- Как задавать размещение, SIMD и аппаратные capabilities без потери переносимости?
- Как устроить ABI и FFI без распространения небезопасных контрактов на всё ядро?
- На каком языке писать промышленный компилятор? Python сейчас служит прототипу семантики; это не обязательство использовать его для production toolchain.