Почему «Глагол»?
«Глагол» — не «Python, переписанный русскими словами». Замысел глубже: взять само устройство русского языка — падеж, вид глагола, согласование — и превратить грамматику в выразительное средство программирования. Там, где другие языки ставят скобки, запятые и служебные значки, русский уже несёт смысл в самой форме слова.
Пометки: есть — работает на площадке; гипотеза — ещё проверяем; эскиз — задуманный синтаксис.
Что даёт грамматика
Падеж задаёт роль аргумента есть
В обычных языках смысл аргумента определяется его местом в скобках: первый параметр — отправитель,
второй — получатель, и горе тому, кто их перепутает. В «Глаголе» роль задаёт падеж или предлог, а не
позиция. Поэтому переведи сумму от Ивана к Петру и переведи к Петру от Ивана
сумму — это одно и то же: слова можно переставлять свободно, потому что «кто», «кому» и «что»
помечены прямо в словах. Целый класс ошибок — перепутанные местами аргументы — здесь просто
не возникает.
Вид глагола говорит, как выполнять действие есть
Русский глагол несёт не только само действие, но и его характер. Несовершенный вид
(помни) в «Глаголе» означает ленивое вычисление — «запомни, но посчитаешь, когда
понадобится». Совершенный вид (запомни, сохрани) означает немедленное
действие с эффектом. Одно-единственное слово передаёт и то, что делать, и то, когда и
как — вместо того чтобы дописывать к коду отдельные пометки вроде @Lazy или
async.
Согласование делает разбор устойчивым к ошибкам есть
В русском языке слова согласуются между собой — эта «избыточность» помогает понять фразу, даже если
часть её искажена. «Глагол» пользуется тем же: если имя написано в косвенном падеже
(владельцу, фамилии), язык связывает его с нужной переменной или полем по
общей основе. А неизменяемость значений и отсутствие глобального изменяемого состояния дают
потокобезопасность по построению и избавляют от null: вместо «пустой ссылки» — честные
типы с вариантами.
Границы внутри программы без цены микросервисов
Это, пожалуй, самая интересная и наименее очевидная сильная сторона «Глагола», поэтому расскажем о ней подробно.
Когда программа вырастает, её хочется разделить на части так, чтобы одна часть не могла запросто
залезть во внутренности другой. В обычной Java для этого есть до обидного мало средств: модификатор
public открывает класс всему миру, а package-private — всем соседям по
пакету. Настоящей, принудительной границы на уровне исходного кода почти нет — договорённости
живут в головах разработчиков и в отдельных линтерах, которые легко обойти. Именно поэтому команды
часто прыгают сразу к микросервисам: разносят код по отдельным сетевым службам просто ради того,
чтобы границу нельзя было нарушить. Но за это приходится платить — сетевыми вызовами, отдельными
развёртываниями, распределёнными сбоями и сложностью, которой в едином приложении не было бы.
«Глагол» предлагает середину между этими крайностями. Он делает границу первоклассной и проверяемой прямо в языке, не выходя за пределы одного процесса:
- Явный список того, что видно снаружи. Модуль (
словарьилираздел) перечисляет в строкеоткрываеттолько те имена, которыми пользуются другие. Всё остальное остаётся его личным делом — снаружи этих имён просто не существует. - Направленная видимость. Можно открыть имя не всем, а конкретному соседу:
открывает создатьСхему для «Миграции». Низкоуровневую деталь видит только тот, кому она действительно нужна, и никто больше. - Запрет циклов между модулями. Если модули начинают ссылаться друг на друга по кругу, «Глагол» это замечает и сообщает — а запутанные круговые зависимости как раз и превращают код в клубок, который невозможно распутать.
- Слои чистой архитектуры. Модуль может объявить свой слой (
слой сущности,правила,адаптеры,инфраструктура), и язык следит за Правилом Зависимостей: внешние слои могут опираться на внутренние, но не наоборот.
В итоге получаются честные архитектурные границы без распределённого налога. А когда какая-то часть действительно перерастёт единое приложение и её понадобится вынести в отдельную службу — чистый модуль с уже описанным «лицом» окажется самой дешёвой для этого заготовкой. Восхождение от модуля к службе становится постепенным и заработанным, а не прыжком в пропасть.
Честно: есть ли такое у других? Отдельные кирпичики — да. В Rust есть точная
видимость pub(in путь); в языках семейства ML (OCaml, Haskell) модуль публикует строгий
интерфейс-подпись; у самой Java с девятой версии есть система модулей (JPMS) с директивами
exports … to. Так что сам механизм границ на уровне исходника — не наше изобретение, и
честно об этом сказать. Необычно другое: «Глагол» собирает всё вместе — видимость, направленный
экспорт, запрет циклов, слои архитектуры и договоры — в единый, встроенный в язык набор, называет его
обычными русскими словами и делает полностью необязательным. Пока программа умещается на экране,
ребёнок не встречает ни одного из этих понятий; они появляются ровно тогда, когда программа из них
вырастает. Такое сочетание доступности и строгости встречается редко.
Статические типы — строгость по желанию есть
«Глагол» — язык с необязательной статической типизацией, и отличает его не сам факт наличия типов, а
отношение к ним. Типы не обязательно писать: язык выводит их из кода сам — понимает, что
5 это число, а "привет" строка, — и ещё до запуска предупреждает о явных
несоответствиях, например о попытке сложить строку с числом. При желании тип можно указать явно, обычным
русским словом типа: число типа Число. А составные типы записываются
родительным падежом, как в живой речи, — список чисел,
отображение из имени в владельца.
Главное здесь — настраиваемая строгость, и это не прихоть, а необходимость. Ребёнку на площадке несоответствие типов покажут мягким предупреждением, но программу всё равно запустят: порог входа важнее придирчивости. А там, где типы по-настоящему критичны — в серьёзной библиотеке или в коде для микроконтроллера, куда без точных типов просто не поместиться, — ту же проверку можно сделать жёсткой: несоответствие станет ошибкой, и программа не запустится. Один и тот же язык обслуживает и школьника, и системного инженера, не заставляя первого платить за строгость, нужную второму.
Где писать типы — правило простое и одно на весь язык: на границе глагола, то есть на его входе и результате. А что происходит внутри, язык вычисляет сам — тело размечать не нужно, даже большое и сложное. Границы размечены, рутина внутри — нет. Отсюда три вещи, которые нужны настоящему коду: ошибка типов встаёт прямо на границе, где её видно; по одной строке-сигнатуре ясно, что глагол принимает и что отдаёт; и типы доезжают до самого низа — в код для микроконтроллера, где число ложится в регистр без лишних обёрток, ради скорости и надёжности на скудном железе.
Пока проверка работает локально — в пределах глагола и его связываний. Сквозной вывод типов по всей программе и опирающиеся на него оптимизации (когда числа перестают «упаковываться» в объекты и арифметика идёт напрямую) — следующий шаг, особенно нужный при компиляции под «железо».
Контрактное программирование есть
«Глагол» поддерживает программирование по контракту (Design by Contract) — подход, который предложил Бертран Мейер в языке Eiffel. Его суть в том, что отношения между вызывающим кодом и функцией описываются как строгий договор: функция вправе рассчитывать на определённые условия при входе — их называют предусловиями, — и обязана обеспечить определённые условия на выходе, то есть постусловия. Для структур данных к этому добавляются инварианты — свойства, которые должны сохраняться при любых операциях. Мы сознательно называем вещь её общепринятым именем, а не выдумываем своё: за термином стоит зрелая инженерная традиция.
В «Глаголе» контракт записывается родными словами прямо в определении глагола:
прим предусловие и постусловие
утроить число -- это число умножить на 3,
требует число больше 0,
гарантирует результат больше число
Слово требует задаёт предусловие — то, что должно быть истинным, чтобы вызов был
законным (здесь: число положительно). Слово гарантирует задаёт постусловие — обещание
о результате, которое может ссылаться на само вычисленное значение через слово результат
(здесь: утроенное положительное число заведомо больше исходного).
Важна сама трактовка: нарушение договора — это не «плохие данные», которые надо молча обработать, а
ошибка в программе. Либо вызывающая сторона передала недопустимое, либо функция не сдержала
обещания. Поэтому при нарушении «Глагол» немедленно останавливается с внятным сообщением, а не
продолжает вычисление с уже испорченным значением, пряча причину сбоя далеко от места ошибки.
Ценность контракта не только в защите. Это ещё и документация, которая не может устареть: в
отличие от комментария, договор проверяется при каждом запуске, поэтому он всегда описывает актуальное
поведение. Здесь контракт смыкается с тестами, которые в «Глаголе» тоже часть языка, а не подключаемая
библиотека: инлайн-проверка проверь: X должно равняться Y и таблицы случаев в блоке
проверка. Вместе договоры и тесты образуют живую исполняемую спецификацию — описание
того, что программа обязана делать, записанное так, что разойтись с кодом оно уже не может.
Контракт естественно связан и с типами. Аннотация типа (число типа Число) — это, по сути,
частный, самый распространённый случай предусловия: «здесь ожидается число». Поэтому в «Глаголе» типы и
договоры — не два разных механизма, а один и тот же приём разной степени подробности.
Честная граница: сейчас договоры проверяет интерпретатор во время выполнения. Компилятор их опускает — ровно так же, как отключаемые проверки-assertions в других языках: в отладочной сборке они ловят ошибки, в боевой не замедляют работу. Проверять контракты статически, ещё до запуска, — направление на будущее, вместе с развитием системы типов.
Доступность: два стиля и два начертания есть
Одну и ту же программу можно писать в двух стилях. Начинающий стиль — словесный
(плюс, больше, равно): его поймёт школьник, и в нём нет ни одного
непонятного значка. Профессионал предпочтёт символьный стиль (+, >,
=): он короче и привычнее опытному глазу. Это не два разных языка, а две записи над одним
ядром — низкий порог входа для детей и лаконичность для опытных сосуществуют без компромисса.
Есть и вторая, независимая ось — начертание, то есть то, какими знаками один и тот же код
показывается на экране. У многих операторов два начертания: клавиатурное (простые ASCII-знаки, которые
легко набрать руками) и типографское (настоящие типографские символы). Тире — и
--, умножение × и *, деление ÷ и /,
сравнения ≤ ≥ ≠ и <= >= !=, ёлочки «имя» и лапки
"имя", подстрочные индексы x₁ и x_1 — это синонимы над одним
ядром. Хранится код всегда в клавиатурном каноне (чтобы файл легко набирался и не зависел от раскладки),
но площадка умеет показать его в типографском виде — переключателем «Вид».
Зачем это особенно важно в паре с ИИ. Человеку удобнее набирать простые знаки с клавиатуры, а вот при
чтении — и человеку, и языковой модели — естественнее видеть настоящие типографские символы:
«имя» в ёлочках вместо кавычек-палочек, ≤ вместо <=,
x₁ с подстрочным индексом. Поэтому «Глагол» разделяет ввод и показ: пишешь как удобно
руками, а агентское начертание переводит это в человекочитаемые типографские знаки, не меняя ни
буквы смысла. Один и тот же код получает два лица — клавиатурное для набора и агентское для чтения.
Один язык — любой бэкенд
Здесь важно поправить возможное недопонимание: «Глагол» задуман не как «язык для JVM с довеском», а как единый язык, который проецируется на любую платформу. В основе — одно дерево разбора (единое представление программы). А уже из него разные «выходы» порождают исполнение под конкретную цель:
- Интерпретатор — мгновенный отклик, идеален для обучения и площадки. есть
- Компилятор в байт-код JVM — скорость и вся экосистема Java рядом. Оба этих выхода сверяются дифференциальными тестами, чтобы вести себя одинаково. есть
- Отечественные процессоры (Эльбрус, Байкал) — через ту же JVM это кратчайший путь к русскому железу. цель
- Микроконтроллеры (STM32, ESP32) — компактный нативный код для «умных» устройств, где важна каждая доля памяти. цель
- Браузер и веб (WebAssembly / JavaScript) — чтобы на «Глаголе» можно было писать и фронтенд. цель
Замысел прост: один язык на все случаи жизни. Программу, написанную по-русски, не нужно переписывать под каждую платформу — меняется лишь то, во что её переводят при сборке. У разных целей будут свои особенности (на микроконтроллере, например, типы придётся указывать строже — иначе не собрать быстрый и компактный код), но язык остаётся одним и тем же.
Работа с ИИ-агентами
«Глагол» создан удобным для пары «человек + ИИ-агент». Роль аргумента задаёт падеж, а не позиция — перепутать отправителя с получателем структурно нельзя: «кто» и «кому» помечены в самих словах. Смысл — падеж, вид, предлог — стоит рядом с аргументом, а не разбросан по коду, поэтому агент удерживает меньше дальних связей и экономит контекст.
Для агента есть компактный файл-навык (SKILL.md): он умещается в контекстное окно, и с
ним агент в наших прогонах решал задачи уровня LeetCode Hard. Код читается как связное описание задачи
по-русски — и человек, и агент проверяют его без мысленного перевода с латиницы.
Компактность кода
«Глагол» стремится быть компактным — не за счёт нагромождения значков, а за счёт грамматики. По ощущению объём и «синтаксический шум» сопоставимы с Kotlin, но строгих замеров пока нет: числовое сравнение с другими языками — это отдельная задача, которую мы честно держим в планах, а не выдаём за сделанное.
За счёт чего экономится место:
- Нет фигурных скобок и точек с запятой — блоки задаются отступом и структурой.
- Именованные аргументы выражены падежами: падежный предлог заменяет громоздкие цепочки-строители
(
Account.builder().from(…).to(…).build()) прямо в самом вызове. - Характер вычисления — видом глагола: вместо
@Lazyилиasyncодно слово. - Тесты встроены в синтаксис: вместо
Assertions.assertEquals()из JUnit —проверь: X должно равняться Y.
Живой пример — факториал есть
Этот код запускается на обоих бэкендах прямо сейчас:
// Java
static long factorial(long n) {
if (n < 2) return 1;
return n * factorial(n - 1);
}
прим Глагол
факториал числа -- это если число
меньше 2, то 1, иначе число
умножить на факториал (число минус 1)
Имя «факториал числа» читается как естественная фраза — существительное с родительным
падежом, без скобок вокруг параметра. И проверку можно написать тут же:
проверь: (факториал 5) должно равняться 120.
Настоящий бэкенд «Клиники» — не игрушечный есть
Самый убедительный пример — это живой бэкенд ветеринарной клиники с настоящей базой данных. Вся схема данных со связями и проверками описывается простой структурой, а репозиторий и внедрение зависимости — буквально по одной строке:
прим Глагол — реальный бэкенд /клиника
сущность «Владелец»:
ключ ид
имя — строка обязательно прим @NotBlank
телефон — строка только цифры прим @Pattern(\d+)
питомцы — много «Питомец» по владельцу прим @OneToMany(mappedBy)
хранилище «Владельцы» хранит «Владелец»:
найти по началу фамилии прим findByФамилияStartingWith
приложение «Клиника»:
встрой владельцы — «Владельцы» прим @Autowired
В Java для того же понадобились бы тяжёлые классы, обвешанные аннотациями (@Entity,
@Table, @Id, @Column, @ManyToOne,
@JoinColumn), геттеры, сеттеры, конструкторы и интерфейсы Spring Data. «Глагол» прячет
весь этот инфраструктурный шум за родными словами (обязательно — это @NotBlank,
только цифры — @Pattern, встрой — @Autowired), а на выходе порождает ровно
тот же самый Spring и JPA.
Уточним честно: «компактнее в разы» — это про декларативный слой данных и инфраструктуры (сущности, репозитории, внедрение, проверки), где Java требует особенно много шаблонного кода. Собственно бизнес-логика короче не обязательно «в разы». Но именно этот инфраструктурный слой в корпоративных приложениях огромен — и выигрыш здесь настоящий и проверяемый: перед вами работающая /demo, целиком порождённая из русского исходника.
Аналог Java Stream API — по-русски есть
Обработку коллекций «Глагол» строит как Java Stream API, но без точек и лямбд: шаги конвейера
связывает родительный падеж, а функцию глагол получает творительным. Жадная часть Stream API уже
работает — отфильтруй/отобрази/сверни (filter/map/reduce),
упорядочение (sorted), первые (limit), любой/все
(anyMatch/allMatch), сцепление … действием (flatMap), группировка/подсчёт (collectors):
// Java (Stream API)
ages.stream()
.filter(a -> a > 17)
.sorted()
.limit(3)
.toList();
прим Глагол — то же, конвейером
первые 3 (упорядочение (отфильтруй возрасты взрослым))
прим где взрослый число -- это число больше 17
А частотный анализ, где в Java нужны Collectors.groupingBy + counting +
max, у нас — одна русская фраза мода слов текста
(пример №108, №106).
Честная ленивость и бесконечные потоки — уже есть. Есть отдельный тип поток —
ленивая, потенциально бесконечная последовательность (аналог Stream.iterate и ленивого
limit): первые 10 (отфильтровывай натуральные, оно простое), решето Эратосфена,
ряды. Ленивость несёт вид глагола (несовершенный = ленивый шаг, совершенный терминал материализует) —
без .stream()/.collect(). Работает на трёх бэкендах; примеры на площадке
(потоки, решето).
Что впереди: ленивое слияние конечного списочного конвейера (fusion — без промежуточных списков) и параллельные потоки. И отдельно — хвостовая рекурсия уже оптимизируется в цикл (пример), так что аккумулятор-идиома безопасна на любой глубине.
Где «Глагол» может проигрывать
Если мерить компактность длиной отдельных слов, русские слова обычно длиннее английских:
сохрани изменённый баланс заметно длиннее, чем saveBalance. Сила «Глагола»
не в коротких словах, а в плотности смысла на строку: грамматика берёт на себя работу
синтаксического сахара. Это утверждение ещё предстоит подтвердить числами — сравнение объёма и числа
лексем с другими языками мы завели отдельной задачей и не выдаём результат заранее.