Nova — обзор
Центральная идея
Сеть, диск, время, случайность, лог, ошибка, мутация — в Nova это
всё эффекты. Функция объявляет в сигнатуре те эффекты, которые
использует сама; вызовы других функций не тащат свои эффекты вверх
(исключение — Fail, ошибки видны транзитивно). У каждого эффекта
есть handler, который перехватывает его операции.
Из одной абстракции (алгебраические эффекты в стиле Koka/Effekt, доведённые до прикладного состояния) следует всё остальное в языке. См. revolutionary.md для развёртки.
effect vs protocol
В Nova два разных способа описать «что-то с операциями»:
- «Как делать что-то» — функция объявляет, что ей нужны
такие-то операции, а какая реализация будет под ними — решает
вызывающий код через
with-блок (например, для прода — Postgres, для теста — in-memory). Это эффект, объявляется черезtype X effect { ... }. - «Что умеет значение» — реализация жёстко привязана к типу:
intхешируется так-то,str— так-то, и менять это нельзя. Это протокол, объявляется черезtype X protocol { ... }.
Когда использовать эффект, а когда протокол в коде: если хочется при тестировании использовать другую реализацию — это эффект. Если при тестировании мы просто работаем со значениями типа, и подменять там нечего — это протокол.
Killer use-case
AI-first программирование. Когда LLM пишет 50–80% кода, языку нужны:
- видимость побочных действий в сигнатуре (эффекты)
- compile-time гарантии вместо runtime-проверок (контракты, capabilities)
- локальность контекста (одна функция понятна без чтения 10 файлов)
- ошибки компилятора как обучающий сигнал для LLM
- стабильность синтаксиса (LLM учится на старых данных)
Все существующие языки спроектированы до AI-эпохи. Nova — первый язык, явно оптимизированный под пару «LLM пишет, человек ревьюит».
Поддерживающие решения
-
Один язык — три режима компиляции: AOT (как Go/Rust), JIT (как .NET), интерпретатор (как Python). Один и тот же исходник.
-
Память: managed по умолчанию (current: Boehm conservative GC; v1.0+: concurrent GC), regions opt-in для real-time. Программист пишет код без префиксов памяти — циклы освобождаются автоматически. Текущее состояние bootstrap-runtime’а (Plan 27, default с 2026-05-11): Boehm GC, measured pauses (см.
nova_tests/concurrency/gc_pause_bench.nv) на x86_64-v3 Windows debug-build:- 10k objects × 20 rounds: max < 16ms, p99 ≈ avg ≈ 0ms (внутри тика GetTickCount64 — Windows timer gran 15.6ms).
- 100k objects × 10 rounds: max < 16ms.
- 1M objects × 3 rounds: max < 16ms.
Это upper bounds через low-res timer; реальные pauses скорее всего меньше. Hi-res measurement (uv_hrtime) — отдельная задача после bootstrap.
Дизайн-цель v1.0+: concurrent GC, p99 < 1ms на типичных workloads (decisions/05-memory.md#d6, Plan 25 G3b).
Escape analysis оставляет на стеке всё, что не утекает (без GC overhead). Для real-time зон (звук, торговля, embedded) — блок
realtime nogc { }(D64), внутриregion { }для arena- allocations.Introspection API (Plan 32):
gc.heap_size(),gc.collect(),gc.live_count()доступны без import. -
Структурная типизация + вывод типов везде.
-
Protocols + data вместо классов. Никакого наследования. Структурные контракты через
protocol(см. decisions/01-philosophy.md#d1, decisions/02-types.md#d42). -
Контракты в сигнатуре.
requires/ensures/invariant— опциональны, но проверяются статически где можно.
Что заимствует у кого
| Фича | Источник |
|---|---|
| Алгебраические эффекты + handler’ы | Koka, Effekt, Eff |
| Скорость компиляции, простой синтаксис | Go |
| Производительность, traits, мономорфизация | Rust |
| Concurrent GC, простота памяти для backend | Go, Java ZGC |
| Pattern matching, ADT, sum-types | OCaml/Rust |
| REPL + AOT в одном | Common Lisp / Julia |
| Регионы памяти | Zig, Odin |
| Structured concurrency, supervision | Erlang/OTP, Swift |
Запуск скрипта как nova file.nv | Python |
| Контракты, refinement-types | Eiffel, Dafny, F* |
| Capability security | E, Pony |
| Time-travel debugging | rr, Hypothesis |
Tooling из коробки
Сегодня (bootstrap) — реализовано в nova CLI (nova-cli/):
nova run file.nv— интерпретатор для скриптовnova build file.nv— статический бинарь через C-backendnova check file.nv— типечек + lint без запускаnova test [filter]— discovery + parallel прогон.nvтестовnova regen-runtime [--check]— регенерацияstd/runtime/*.nvstubs изruntime_registry.rs(Plan 13)- Структурированные ошибки с EXPECT-маркерами для negative-тестов (D89)
Roadmap (не в bootstrap):
nova fmt,nova lint,nova bench,nova docnova check --fragment '...'— типечекинг одной функции без проектаnova run --record trace.nrec/nova replay trace.nrec— time-travel- LSP — часть компилятора
- Пакетный менеджер — content-addressed (как Deno + Nix)
- Hot reload в dev-режиме
- AI-friendly патчи в diagnostic’ах (для LLM)
Что выкинуто из обычных языков
- Заголовочные файлы, namespaces, modules-vs-packages — один файл = модуль
- Null — только
Option[T] - Исключения как невидимое control flow — только эффект
Fail[E] async/awaitключевые слова — suspension это ambient runtime (D62), эффекты в типах:Net,Io,Db- Перегрузка операторов на произвольные типы
- Макросы как препроцессор — только typed comptime (как Zig)
- Глобальное изменяемое состояние —
mutполя/параметры (локально) или специализированные state-эффекты (Counter, Cache) - DI через рефлексию — зависимости в эффектах или параметрах
- Mock-библиотеки — handler’ы из языка
- Скрытые импорты — каждый идентификатор виден откуда
Зарезервированные identifier’ы
Помимо grammar-keyword’ов (fn, type, effect, handler, let,
if, match, return, … — около 38 слов), Nova имеет
identifier’ы с зарезервированной семантикой. Они парсятся как
обычные имена, но компилятор знает их специальное значение в
определённых контекстах.
| Identifier | Категория | Где валиден | См. |
|---|---|---|---|
Self | referential type | в любом type-контексте — refers к receiver-типу метода / типу удовлетворяющему protocol’у | D66 |
any | top-type | везде; runtime type-tag для downcast’а | D54 |
never | bottom-type | return type не-возвращающих функций (throw, panic, loop) | D26 |
Option[T], Some, None | sum-тип в prelude | везде | D26 |
Result[T, E], Ok, Err | sum-тип в prelude | везде | D26 |
Error | record-тип в prelude | для throw err | D26 |
RuntimeError | sum-тип в prelude | bottom-уровневые runtime-ошибки | D26 |
RuntimeNoneError | unit-тип в prelude | бросается через expr!! на Option | D85 |
Effect[E, IRT] | first-class тип handler’а эффекта E с типом interrupt-VAL IRT (default never через D88); sugar Effect[E] ≡ Effect[E, never] | везде | D61, D87, D88 |
Fail[E], Fail | стандартный эффект | в effect-row сигнатуры | D25, D65 |
Io, Net, Db, Fs, Time, Random, Log, Trace, Ask[T], Alloc[R], Detach, Blocking | стандартные эффекты | в effect-row сигнатуры | D2 (REVISED), D50 |
int, i8-i64, u8-u64, f32, f64, str, bool, byte | примитивные типы | везде | D44, D27 |
Эти identifier’ы можно переопределить локально (например, тип
Net пользовательской библиотеки), но это — анти-паттерн. Линтер
выдаст warning.
Главные trade-offs
- Algebraic effects сложны в реализации — это передовой край PL, Koka работает 10+ лет и всё ещё академический.
- Понимание эффектов — порог входа — решается только качеством сообщений компилятора. Если они академически точны и человечески непонятны — язык мёртв.
- Performance эффектов требует агрессивной оптимизации (статический handler-резолюшн, инлайнинг).
- Ставка на AI-кодинг как доминирующий тренд — статистически вероятна, но не гарантирована.
- 9 из 10 таких проектов проваливаются. Это нормальный риск революционной попытки. Альтернатива — гарантированный «ещё один Nim».