Руководство
Как писать на «Глаголе» и как пользоваться площадкой. Язык живой — примеры ниже исполняются на площадке прямо сейчас.
У языка два стиля — регистра сложности над одним ядром: начинающий
словесный, для школьников (плюс, равно, больше) и
профессионал компактный, символьный (+, =,
>). Обе формы — синонимы; выбираешь по вкусу.
Язык
Как исполняется программа пример №79
Твой файл — это и есть главное. Отдельного «главного класса»/main нет: запускаешь
файл — он исполняется сверху вниз. Действия (напечатай …, вызовы) и значения
(x -- это …) выполняются по порядку.
Но определения — глаголы и типы — «подняты»: видны во всём файле независимо от места, как методы в Java. Поэтому можно писать главную логику сверху, а вспомогательные глаголы — ниже:
напечатай (факториал 5) прим 120 — глагол ниже, но виден факториал числа -- это если число равно 0, то 1, иначе число умножить на (факториал (число минус 1))
Коротко: «рецепты» (глаголы/типы) — вне порядка (как методы), «действия и значения» — по порядку (как строки в теле метода). Значение нельзя использовать до его связывания — оно вычисляется на своём месте.
Модуль — набор определений (раздел/библиотека): в нём только
глаголы/типы, без исполняемого кода — подключи ничего «не запускает». А запуск целого
приложения (сервера) — отдельной строкой-примитивом поручи приложение … (в работе).
Сущность (неизменяемое имя) пример №2
Связывание значения с именем — определение, не присваивание. Переопределять нельзя.
приветствие -- это "Здравствуй, мир!" напечатай приветствие прим Здравствуй, мир!
Диграф -- нормализуется в тире «—» (как TeX). Комментарий — слово
прим до конца строки.
Имена сущностей пример №30
Имя — смысловое существительное (ширина, итог, цена),
а не x, x2. Имя можно склонять — падеж несёт роль аргумента, — поэтому
цифру к слову приклеивать нельзя (см. ниже). Нужны две похожие величины — дай каждой своё имя:
ширина -- это 3 высота -- это 5
Богатая палитра именования — одно имя может иметь несколько форм над одним ядром:
- Дефис — только для НАСТОЯЩИХ составных слов, как «бизнес-процесс»:
бизнес-процесс -- это 12. Для перечисления величин он не нужен — бери буква-индекс (ниже). (Двойной дефис--— связка «— это», не часть имени.) - Цифра, приклеенная к слову — ошибка (категориальная: имя должно склоняться).
число2,шаг1— не имена. Вместо — буква-индекс (сторона А, заглавная буква), смысловое слово (налог) или индекс-символ (ниже). Это осознанный разворот прежнего решения: цифры-в-именах отменены в пользу склоняемости. - Индекс-символ для формул —
x_1,x_2,x_i: основа — один символ, индекс через подчёркивание. В агентском начертании — подстрочныйx₁(нормализуется вx_1). Подробнее — пример №46. - Многословное имя — в ёлочках
«…», рядом в скобках аббревиатура/синоним, зови любой формой:«длина стороны» (ДС) -- это 4. Подробнее — пример №43, №44. - Имя функции — тоже смысловое, по роду работы ЭВМ: действие — глагол
(
удвоить), значение — существительное + родительный (квадрат числа). «Вычислить» подразумевается.
Многословные имена пример №73
Часто нужны две-три похожие величины. Не число1/число2 (цифра к слову — ошибка) и
не костыль-дефис — а естественные русские формы:
- Буква-индекс — слово + одиночная ЗАГЛАВНАЯ буква, без кавычек:
Пункт А -- это 10,Пункт Б -- это 32, ссылкаПункт А. Регистр разводит имя и предлог: заглавная буква — часть имени (сторона В), строчный предлог (в/с/по/…) — роль, не имя. Глагол следующую букву не поглощает, поэтомунапечатай Аработает как печать значенияА. - Имя-фраза из полных слов — в ёлочках
«…»:«первое слагаемое» -- это 100, ссылка«первое слагаемое»даёт значение (а если такого имени нет — это просто строка). Кавычки для фразы из двух полных слов опциональны по смыслу, но наглядны; буква-индекс кавычек не требует.
Видимость имени человеку даёт подсветка (в редакторе/на площадке) и выделение в
агентском выводе, а не кавычки: канон хранения — голый (Пункт А). Отдельно — формульный
индекс-символ x_1 (см. пример №46): там основа —
один символ; здесь — целое слово с буквой-меткой.
Строгое и ленивое — когда вычисляется пример №6
Вид глагола решает, когда считать значение:
| Форма связывания | Когда вычисляется |
|---|---|
имя -- это … | лениво — при первом обращении, с запоминанием |
… помни как имя | лениво (несовершенный вид) |
… запомни / запиши / зафиксируй как имя | строго — сразу (совершенный вид) |
Ленивое (-- это, помни) считается при первом обращении и запоминается
(мемоизация) — поэтому им можно ссылаться вперёд. Строгое (запомни) — «сделано
сейчас». У глаголов: инфинитив → аргументы жадные, отглагольное существительное → ленивые.
Честная ленивость (пример №109): неиспользуемое ленивое не вычисляется вообще — даже если внутри ошибка. «Ошибка» ниже не срабатывает, потому что её ни разу не тронули; программа спокойно идёт дальше — одинаково на всех трёх бэкендах (интерпретатор, компилятор в байт-код, браузер):
1 / 0, помни как ошибка прим деление на ноль — но её не используем напечатай "работаем дальше" прим не тронута → не считается, падения нет
Потоки — бесконечные ленивые последовательности пример №110
Поток — ленивая, потенциально бесконечная последовательность (аналог Java Stream, но по-русски). Он бесконечен «на бумаге»; считается ровно столько, сколько возьмёт терминал. Печатать поток целиком нельзя — он потенциально бесконечен; язык честно об этом скажет и подскажет взять конечное.
- Генераторы:
натуральные(1, 2, 3, …),натуральные от N;повторяй ДЕЙСТВИЕМ от НАЧАЛА(iterate: начало, f(начала), f(f(начала))…) — пример №111. - Ленивые шаги (поток→поток):
отображай(map),отфильтровывай(filter) — несовершенный вид несёт ленивость; результат снова поток. - Терминалы (берут конечное, короткое замыкание):
первые N(take),пока УСЛОВИЕМ(takeWhile) — пример №112; кванторылюбой/все/никакой ПОТОК УСЛОВИЕМ(илилюбой из ПОТОКА …— звучит по-русски; стоп на первом решающем). - Заглянуть внутрь:
голова потока— первый элемент,хвост потока— остаток (снова поток). Отсюда идиома «первое подходящее»:голова (отфильтровывай … УСЛОВИЕМ). - Склейка:
спари ПОТОК с ДРУГИМ(zip) → поток пар; например пронумеровать —спари поток с натуральными(значение + номер).
Условие пишется прямо на месте (пример №114):
через неявный элемент оно, без заведения предиката —
отфильтровывай натуральные, оно меньше 5, пока натуральные, оно меньше 10.
Маркер — запятая перед оно; работает и над обычным списком (отфильтруй … , оно больше 2).
Именованный предикат в творительном (… небольшим) тоже остаётся допустим.
Свёртка потока — сперва возьми конечное (сумма (пока натуральные небольшим)):
свёртка обязана пройти все элементы, а поток бесконечен. Кванторы все/никакой
над бесконечным потоком без контрпримера не завершатся — как любой ленивый язык (на площадке runaway
останавливается лимитом шагов песочницы, а не висит).
Шаги конвейера чисты: действие/условие — значение-глагол, а он не бывает повелительным, поэтому
напечатай/эффекты в ленивый шаг не попадают — печатай над готовым результатом (в терминале).
пока — декларативный двойник счётного цикла: порождаем числа и останавливаемся по условию,
а накапливаем свёрткой (сумма), без единой изменяемой переменной. Витрина силы —
решето простых: бесконечные натуральные фильтруем предикатом простоты и берём первые N
(пример №113).
Ленивое слияние конечного списочного конвейера и параллелизм — следующие шаги. Здесь ленивы связывания и потоки; обычные списки — строгие.
Объединение строк пример №3
Роль аргумента несёт предлог/падеж, а не позиция — порядок слов свободен (пример №4 — тот же вызов в разном порядке слов).
соедини "Привет" с ", мир!", запомни как фразу напечатай фразу прим Привет, мир!
Операции над строкой: длина строки, символ N из строки (символ
как строка), цифра N из строки (цифра как число; отсчёт с 0), слова строки
(разбить текст на список слов по пробелам). Примеры —
Расшифровка (#91), частотный анализ текста (#106).
Строки: поиск, замена, образцы пример №107
Полный набор строковых операций. Все преобразования дают новую строку (исходная не меняется); отсчёт позиций — с 1, границы включительно. Роль аргумента несёт предлог, порядок слов свободен.
- Сравнение по алфавиту:
раньше/позже— синонимыменьше/большедля строк ("арбуз" раньше "банан"). Основа сортировки. - Часть строки:
подстрока строки с N по M(включительно). - Проверки (да/нет):
начинается строки с "X",заканчивается строки на "X",содержит строку "X". - Место:
место "X" в строке→может быть(нечтопозиция илиничего). - Преобразования:
прописные строки(ВЕРХНИЙ регистр),строчные строки(нижний),обрезка строки(убрать пробелы по краям),замена "A" на "B" в строке(все вхождения). - Разрез и сборка:
разрез строки по "X"→ список; обратно —соедини список через "X". - Образцы (регулярные выражения):
совпадает строки по образцу "…"(целиком),найди по образцу "…" в строке(первое совпадение →может быть),замени по образцу "…" на "B" в строке(все). - Разбор (пример №116):
символы строки→ список букв (строка как коллекция символов, для перебора/потока);число из строки(для ЛИТЕРАЛА свяжи его сперва:цифры -- это "42"→число из цифр; строка в кавычках сразу после «из» читается как имя модуля) →может быть(нечто N, если это число, иначеничего) — парсинг.
напечатай (подстрока "пирог" с 3 по 5) прим рог напечатай (замена "а" на "О" в "банан") прим бОнОн напечатай (соедини (разрез "а,б,в" по ",") через "-") прим а-б-в напечатай (замени по образцу "\\d+" на "N" в "год 2026") прим год N
Образец (regex) — обычная строка-значение, поэтому слэш в ней удваивается:
"\\d+" означает «одна и более цифр». Пока это сырой синтаксис регулярных выражений;
читаемый русский язык образцов — в планах. Java- и браузерный движки чуть расходятся на экзотике —
опирайтесь на общий набор (\d \w \s . * + ? {n,m} [] () |).
Списки-значения пример №14
Список — первоклассное значение: конструктор всё:, неизменяем. Операции-имена
управляют родительным падежом — как в русском: «длина ряда», а не «длина ряд».
ряд -- это всё: 3, 1, 2 напечатай (длина ряда) прим 3 напечатай (первый из ряда) прим 3 напечатай (хвост ряда) прим [1, 2]
Скобочный литерал пример №126 — тот же список, но короче и симметрично показу (пишу = вижу):
[3, 1, 2] вместо всё: 3, 1, 2. Вложенные массивы читаются как на бумаге —
[[1, 2], [3, 4]]; словесное всё: остаётся (для начинающих). Как аргумент глагола
составное значение берётся в скобки (применение крепче): напечатай ([1, 2, 3]), а x[i]
сразу после значения — это индекс. Отрицательные — унарный минус: -1,
-(a + b). Доступ по номеру (с 1) безопасен (вне границ → ничего) и чейнится
у вложенных: сетка[i][j] даёт нечто(элемент) или ничего.
Свой глагол — жадный и ленивый пример №8
Инфинитив называет, повелительное вызывает. Форма заголовка = вид вычисления: инфинитив → жадный, отглагольное существительное → ленивый (аргументы считаются при обращении).
удвоить строку -- это соединить строку со строкой удвой "ля", запомни как эхо напечатай эхо прим ляля
Имя функции — по роду работы: действие называют глаголом (удвоить,
отсортировать), а значение — существительным/прилагательным с аргументом в родительном
(факториал числа, квадрат числа, максимум ряда). «Вычислить»
подразумевается — машина и так вычисляет; довольно назвать, ЧТО и ЧЕГО. Параметр в родительном
(числа) в теле зовётся именительным (число).
квадрат числа -- это число умножить на число напечатай (квадрат 8) прим 64
Несколько аргументов номинал соединяет союзом и
(среднее длины и ширины). Булеву функцию можно пометить -- это предикат.
Параметры — по падежу, порядок свободен; локальные — в придаточном где.
Локальные связывания — «где» пример №54
Придаточное где даёт подвыражению имя — тело читается как фраза (как
where в Haskell). Пишется в конце тела через запятую; связанное имя видно во всём теле.
Одно где на тело.
итог заказа -- это заказ плюс доставка, где доставка -- это заказ разделить на 10 напечатай (итог 100) прим 110
Работает и после многострочного если … иначе … (запятая, затем где):
скидка суммы -- это если сумма больше 100, то сумма минус льгота, иначе сумма, где льгота -- это сумма разделить на 10
Не путать с где: в шапке таблицы блока проверка — там
двоеточие вводит колонки случаев, это другое употребление слова.
Функции высшего порядка пример №74
Глагол — это значение: его можно передать другому глаголу и там применить. Роль аргумента несёт падеж, а не значок. Три роли — три падежа:
- данные (над чем работаем) — винительный у повелительного (
список), родительный у номинала (списка); - функция (посредством чего) — творительный:
удвоением,суммой,барьером.
Так свёртки (map/fold/filter) становятся библиотекой на Глаголе, а не встроенными. «Отобрази» применяет переданное действие к каждому элементу:
удвоение числа -- это число умножить на 2 отобрази список действием -- это если список пуст, то пустой список, иначе добавить (действие (первый списка)) к (отобрази (хвост списка) действием) напечатай (отобрази (всё: 1, 2, 3) удвоением) прим [2, 4, 6]
Значение-глагол передают только по имени — правильно названный глагол уже имеет
регулярный творительный (§86); лямбд и значков нет. Вычисленный глагол сперва связывают с именем,
потом передают. Голова вызова применяется n-арно: (действие (первый списка)).
Локальный где-глагол захватывает окружение — это замыкание. «Граница»
строит предикат «мерка», поймавший порог; связываем его именем барьер и
передаём в фильтр:
граница порога -- это мерка, где мерка числа -- это число больше порога барьер -- это граница 2 напечатай (отфильтруй (всё: 1, 2, 3, 4, 5) барьером) прим [3, 4, 5]
Глагол можно и вернуть: «сумматор» отдаёт замыкание-прибавитель, помнящее свой шаг.
сумматор шага -- это надбавка где надбавка числа -- это число плюс шаг прибавитель -- это сумматор 5 напечатай (прибавитель 10) прим 15
Бэкенды v1 — интерпретатор и браузерный JS (замыкания нативны). Компилятор в байт-код поддержит значение-глагол позже (пока — интерпретатором).
Обработка ошибок пример №75, №76, №77, №78
«Что-то пошло не так» бывает трёх разных сортов, и путать их вредно — поэтому и механизма три. Разница простая, по-житейски:
- Значения могло и не быть — это нормально. Искал имя в списке, а его там нет; у пустого списка
нет «головы». Это не поломка — просто нет. Такой глагол возвращает
может быть(нечто (x)илиничего). - Операция могла не удаться, и важно сказать ПОЧЕМУ. Разобрать число из кривой строки, поделить
на ноль в бизнес-логике. Тут возвращают
итог—успех (x)илисбой (причина), чтобы вызвавший узнал причину и решил, что делать. - Так вообще быть не должно — это БАГ. Обратились к 10-му элементу списка из трёх, нарушили
договор
требует. Программист ошибся сам — программа должна упасть громко (брось «причина»), чтобы баг заметили и починили, а не тихо поехала дальше с мусором.
Отсюда правило выбора: ждёшь, что значения может не быть → может быть; операция может
провалиться и причина важна → итог; «этого не может случиться» → паника брось.
Плюс отдельный, четвёртый случай — чужой Java-код: он кидает исключения по-своему, поэтому на границе
оборачиваешь его в попытка (…) и получаешь наш итог.
Зачем так, а не просто «исключения как в Java»? Потому что ожидаемую ошибку-значение
видно прямо в результате — её нельзя молча забыть (компилятор/выбор заставят разобрать оба
случая). А падение (паника) остаётся только для настоящих багов. Так обычный сбой и баг не путаются, и
программа ведёт себя предсказуемо. Ниже — те же три механизма подробно:
- ожидаемая ошибка → значение.
может быть—нечто (x)/ничего(значение есть или нет, без причины);итог—успех (x)/сбой (причина)(провал с названной причиной). Разбираешь обычнымвыбор; - баг → паника.
брось «причина»(и договортребует) роняет программу — ловит только верхний хост (площадка показывает ошибку). Хочешь восстановиться — верни значение, не паникуй; - чужое Java-исключение →
итог. Формапопытка (…)вокруг interop-вызова ловит исключение и отдаётсбой (причина)(нашу панику не глотает).
деление числа и делителя -- это если делитель равно 0, то сбой "на ноль нельзя", иначе успех (число разделить на делитель) печать итога -- это выбор итога: успех (частное), то напечатай частное сбой (причина), то напечатай причину печать (деление 20 4) прим 5 печать (деление 20 0) прим на ноль нельзя
На границе с Java — попытка превращает исключение в значение (демо так восстанавливается
после ошибки БД, а не падает):
разбор строки -- это выбор (попытка (создать «java.lang.Integer» строки)): успех (число), то соедини "число " с строкой сбой (причина), то соедини "не число: " с строкой
А брось — для багов, «этого не может быть»: программа падает с сообщением,
чтобы ошибку заметили и починили (обычные сбои возвращай значением, не паникой):
корень числа -- это если число меньше 0, то брось "корень из отрицательного — так не бывает", иначе число напечатай (корень 9) прим 9 напечатай (корень (0 минус 4)) прим ⚠ падение с сообщением
Безопасные обёртки пишутся НА Глаголе, а не встроены: достань список -- это если
список пуст, то ничего, иначе нечто (первый списка). Стандартные первый/деление на
неверном входе паникуют (как и раньше). Компилятор в байт-код: значения/выбор/брось
— да; попытка — позже (пока интерпретатором).
Составные имена, синонимы, аббревиатуры пример №43, №44
Многословное имя (функции или сущности) заключают в ёлочки «…» —
так пишет ИИ — или в лапки "…", которые легче набрать с клавиатуры. Обе
формы задают одно имя, зови любой:
«площадь прямоугольника» ширины и высоты -- это ширина умножить на высота напечатай ("площадь прямоугольника" 3 и 4) прим 12
Рядом с именем в скобках — синонимы/аббревиатуры (можно несколько), и зовёшь любым. Работает и для существительных, и для глаголов:
колхоз (КХ, община) -- это 100 напечатай КХ → 100 «наибольший общий делитель» (НОД) числа и делителя -- это … напечатай (НОД 48 и 36) прим 12
Отдельные примеры по возможностям именования: №45
имя-номинал, №46 индексы-символы (x_1),
№47 многоаргумент через «и», №48
предикаты.
Предложные роли пример №49
Аргумент можно пометить предлогом (в/на/по/над/под/за/до) — тогда роль
читается по-русски, а порядок свободен: метка опознаёт аргумент, а не позиция.
Предлог стоит и в определении, и в вызове; имя-слот в вызове — читаемая метка:
возвести число в степень -- это … напечатай (возвести 2 в степень 10) прим 1024 напечатай (возвести в степень 10 2) тот же результат — порядок свободен
Легаси-предлоги (с/из/от/к) сохраняют прежний смысл
(соедини A с B, первый из ряда).
Символы-двойники пример №50
Один и тот же оператор пишут любым из двух знаков — типографским (его подставляет ИИ из всей таблицы Unicode) или клавиатурным (его легко набрать руками). Обе записи — синонимы над одним ядром:
напечатай (7 × 5) то же, что 7 * 5 → 35 напечатай (10 ÷ 2) то же, что 10 / 2 → 5 если a ≤ b то … то же, что a <= b
Все пары «типографский ↔ клавиатурный» (левый знак ставит ИИ, правый набирают руками):
- связка «это»:
—↔-- - умножение:
×↔* - деление:
÷↔/ - меньше-равно:
≤↔<= - больше-равно:
≥↔>= - не равно:
≠↔!=(все варианты «не равно» — ниже)
Пиши как удобно — многовариантные операторы пример №51
Там, где в других языках спорят о «правильном» знаке «не равно», «Глагол» принимает все привычные варианты сразу — из C/Java, из Паскаля/Haskell, из SQL. Читаем либерально, а печатаем канон (закон Постела):
Все синонимы «не равно» — любой читается одинаково:
≠— типографский (его ставит ИИ)!=— из C, Java, JavaScript, Python/=— из Haskell, Ada<>— из Паскаля, SQL, Бейсика
Все четыре — одно и то же сравнение. Пиши тем, что под рукой; на смысл это не влияет.
Печатается канон (закон Постела): человеку — !=, ИИ — ≠.
Профили отображения — «Вид»
Исходники хранятся в клавиатурном ASCII (это канон: --, *,
<=, !=) — их легко набрать и читать в git. Селектор
«Вид» на площадке показывает тот же код в другой форме, не меняя смысла:
- клавиатурный — как в файле (по умолчанию);
- типографский — операторы типографскими знаками:
—,×,÷,≤,≥,≠; а матричный литерал[[…]](≥2 строк) рисуется 2D-блоком с высокими скобками:[[0, -1], [1, 0]]→⎡ 0 −1 ⎤/⎣ 1 0 ⎦. Канон в файле — ASCII[[…]]; при запуске вид сворачивается обратно (нормализуется).
Обе формы — один и тот же разбор и исполнение; перевод делает общий движок на сервере (одна проверенная истина, а не «на глаз»). На сравнительных примерах (№50, №51), которые нарочно показывают обе записи сразу, селектор скрыт.
Интеграция с Java (Spring / JPA)
«Глагол» живёт на JVM, поэтому дотягивается до всей экосистемы Java — от вызова готовых классов до полноценного веб-приложения на Spring Boot с базой данных. Разделы ниже идут по нарастанию: вызвать Java → свой класс → аннотации → методы-контроллеры → репозиторий и БД → родная русская поверхность над всем этим → связи и поиск. Живой пример — CRUD «Питомцы» на настоящей БД, написанный целиком на «Глаголе».
Java-interop — зовём Java пример №52
«Глагол» на JVM, поэтому из него можно вызывать готовый Java-код (первый шаг к Spring/Jmix).
Ядро-типы (число/строка/логическое/список) авто-мостятся; произвольный Java — явно:
- псевдоним класса:
подключи «java.util.ArrayList» как Набор; - конструктор:
создать Набор(илисоздать «FQCN» аргы); - статический вызов:
у «java.lang.Math» вызвать «max» 3 7; - экземплярный вызов:
у набор вызвать «add» 5.
подключи «java.util.ArrayList» как Набор набор -- это создать Набор у набор вызвать «add» 5 напечатай (у набор вызвать «size») прим 1 напечатай (у «java.lang.Math» вызвать «max» 3 7) прим 7
Работает на обоих бэкендах (единый рефлективный движок). Исключение Java становится ошибкой «Глагола». Пока это только ВЫЗОВ Java; определение классов/аннотаций (Spring/Jmix-приложение) — следующими шагами. Дробные типы и мост для коллекций — позже.
Методы записи — параметры и тело пример №56
У записи бывают методы — так наш класс становится, например, Spring-контроллером.
Метод объявляется в блоке записи: метод «имя» (парам — тип, …) возвращает тип: тело.
Параметры необязательны; тело — одно выражение (его значение возвращается). Имя параметра Spring
находит по имени (для @RequestParam), а в теле на параметр ссылаются
в любом падеже (по основе: кличку находит кличка):
запись «Умножитель»: метод «удвоить» (значение — число) возвращает число: у «java.lang.Math» вызвать «multiplyExact» значение 2 умножитель -- это создать «Умножитель» напечатай (у умножитель вызвать «удвоить» 21) прим 42
Тело метода — ограниченное выражение: литерал, ссылка на параметр, создать …
и у … вызвать … (interop). Этого хватает на контроллер, который принимает данные и
зовёт бэкенд. На этом механизме работает живой мини-CRUD /питомцы:
метод добавить (кличка) мутирует хранилище (совершенный вид «добавь» = эффект), метод список его читает. Метод с @SpringBootApplication реально
поднимает Spring Boot (доказано автотестом). Работает на обоих бэкендах.
Хранилище — персистентность (БД) пример №57
Сущность (запись, помеченная @Entity) сохраняется в базу через хранилище —
репозиторий Spring Data. Объявление одной строкой:
запись «Владелец» помечена «jakarta.persistence.Entity»: идентификатор — число помечено «jakarta.persistence.Id» и «jakarta.persistence.GeneratedValue» имя — строка хранилище «Владельцы» хранит «Владелец» → interface extends JpaRepository<Владелец, Long>
Контроллер получает хранилище как поле-зависимость (тип — в ёлочках, помечено
@Autowired) и зовёт его методы (save, findAll, count,
deleteById…) прямо в теле метода — так же и параметр может быть типизирован сущностью
(Spring свяжет поля запроса):
запись «Владельцы-сервис» помечена «...RestController»: репозиторий — «Владельцы» помечено «...Autowired» метод «создать» (владелец — «Владелец») возвращает строку помечен «...PostMapping» ("/владельцы"): у репозиторий вызвать «save» владельца метод «сколько» возвращает число помечен «...GetMapping» ("/сколько"): у репозиторий вызвать «count»
Полное приложение (запись с @SpringBootApplication) поднимается на Spring Boot
+ H2: POST реально пишет владельца в базу, GET читает — round-trip через БД доказан автотестом
(ПерсистентностьE2ETest). Ключ реализации: эмитированные классы материализуются на
диск, и Spring/Hibernate находят их сканированием как обычный classpath. На площадке показаны только
ОБЪЯВЛЕНИЯ (сам Spring/БД в песочницу не тянем); запуск приложения — как jar/тест.
Родная поверхность — без англицизмов пример №58, №59
Всё выше можно писать по-русски, без getX/setX и Java-имён аннотаций —
под капотом остаётся тот же байт-код. Доступ к полям записи:
- чтение — родительный (форма «значение»):
(имя владельца)вместоу владелец вызвать «getИмя». В позиции аргумента — в скобках, как любое вложенное применение; - запись — совершенный вид (эффект):
задай владельцу имя "Иван"вместо сеттера.
Правило: если голова — объявленный глагол, это вызов; иначе — доступ к полю. Склонённое
имя переменной резолвится по основе (владельцу/владельца → владелец).
Конструирование с полями пример №82 —
собрать значение целиком ОДНИМ неизменяемым выражением (вместо «создать + задать + задать…»). Поле метит
творительный падеж («чем»): то, что другие языки пишут значком (имя= в Scala/Kotlin,
имя: в Ruby/JS), Глагол выражает падежом:
владелец -- это создать «Владелец» с именем "Пётр", фамилией "Петров", городом "Тула"
Имя поля резолвится по основе/лемме (именем→имя,
ценой→цена). Идиоматичная поверхность — неизменяемая; задай
(мутация) остаётся низкоуровневым interop-люком.
Неизменяемое обновление пример №83 —
копия <значения> с полем значение, … создаёт НОВУЮ запись с изменёнными полями, а
исходная не тронута (как .copy в Scala, record-update в Haskell):
переехавший -- это копия владельца с городом "Сочи"
Слово-голова копия снимает неоднозначность с предлогом с
(роль-аргумент в вызовах). Оба JVM-бэкенда.
Родные слова (гибрид: ключевые слова + расширяемые привязки):
сущность «Владелец»: = запись + @Entity ключ идентификатор = число + @Id + @GeneratedValue имя — строка хранилище «Владельцы» хранит «Владелец» контроллер «Владельцы-сервис»: = запись + @RestController встрой репозиторий — «Владельцы» = поле + @Autowired при создании "/владельцы" метод «создать» (владелец — «Владелец») возвращает строку: у репозиторий вызвать «save» владельца при получении "/сколько" метод «сколько» возвращает число: у репозиторий вызвать «count»
Слова сущность/контроллер/приложение (точка входа Spring
Boot)/ключ/встрой/при получении|создании|замене|удалении — ядро. Прочие
Java-аннотации подключаются привязкой
пометка «FQCN» как слово (расширяемо, как подключи…как): напр.
столбец→@Column. Низкоуровневая форма (запись, помечена «FQCN»,
у…вызвать) остаётся — это двойная запись над одним ядром.
Веб-возврат: контроллер отдаёт данные пример №81
Метод может вернуть не только строку/число, но и саму сущность и
список сущностей — именно это нужно контроллеру, чтобы отдать данные, а Spring MVC сам превратит
их в JSON:
- одна сущность —
возвращает «Владелец»; - список —
возвращает список «Владелец»(родительная генерик-форма, как в статических типах).
контроллер «Владельцы-сервис»: встрой репозиторий — «Владельцы» при получении "/владельцы" метод «список» возвращает список «Владелец»: у репозиторий вызвать «findAll» = List<Владелец> → JSON-массив
Возврат — обычный JVM-тип (сущность или List), библиотека сериализации в ядре
не «зашита». Круговые связи (владелец↔питомец) при печати в JSON зациклятся — один конец связи
помечают, чтобы он себя не пересказывал, тем же расширяемым механизмом привязки:
пометка «com.fasterxml.jackson.annotation.JsonIgnore» как скрыто, затем
владелец — один «Владелец» помечено скрыто. Никакой магии: аннотация подключается из кода, а не из
компилятора. Доступ к полю сущности — снаружи метода (в теле метода поддержаны условие/арифметика/
вызовы, но не чтение полей — берите поле у возвращённого значения: (имя владельца)).
Связи и CRUD пример №60
Связь «один-ко-многим» (у владельца много питомцев) — поле-коллекция
питомцы — много «Питомец»: под капотом List<Питомец> +
@OneToMany(cascade=ALL, fetch=EAGER). Список кладут через задай:
сущность «Владелец»: ключ идентификатор имя — строка питомцы — много «Питомец» задай владельцу питомцы (всё: барсик, рекс) напечатай (у (питомцы владельца) вызвать «size») прим 2
Двунаправленная связь (пример №61): у питомца — свой владелец
(владелец — один «Владелец» = @ManyToOne), у владельца — его питомцы
(питомцы — много «Питомец» по владельцу = @OneToMany, обратная сторона; по <поле>
указывает поле-ссылку в дочерней сущности). Поиск — производные запросы: в блоке хранилища
найти по фамилии даёт метод, который Spring Data реализует сам (findByФамилия):
хранилище «Владельцы» хранит «Владелец»: найти по фамилии прим точное совпадение найти по началу фамилии прим по префиксу (StartingWith)
Связь «многие-ко-многим» (у ветеринара много специальностей, и наоборот) —
специальности — общие «Специальность» (= @ManyToMany). Валидация — родными
словами после типа: фамилия — строка обязательно (= @NotBlank),
телефон — строка только цифры (= @Pattern). Полная модель Petclinic (владельцы, питомцы,
виды, приёмы, ветеринары, специальности) — пример №62. Полноценный живой
веб-CRUD над этой моделью — /demo: список и поиск владельцев по фамилии,
добавление с валидацией, карточка с питомцами и приёмами, ветеринары со специальностями — всё на реальном
JPA/H2, эмитировано из клиника-app.гл.
U/D над репозиторием — родные глаголы совершенного вида (эффект): сохрани
владельца в репозиторий (create/update) и удали владельца из репозитория
(delete). Это сахар над у репозиторий вызвать «save»/«delete». Полный цикл (владелец с
питомцами пишется в БД каскадом и читается назад) доказан автотестом СвязиJpaTest;
на площадке репозитория/БД нет — показано построение связи в памяти.
Ещё о языке
Дальше — возможности самого «Глагола», не связанные с Java: условия и выбор, собственные типы данных (записи и суммы вариантов), истинность, арифметика, тесты. Всё это работает и в интерпретаторе, и в компиляторе, без единой строки Java.
Условия пример №9
Условие — выражение если … то … иначе; «иначе» обязательно. Запятые по вкусу.
знак -- это если 7 больше 0, то "плюс", иначе "не плюс" напечатай знак прим плюс
Выбор — многоветочный пример №24
Замена цепочек если … иначе если …. Единая форма (как Kotlin when):
сопоставление со значением, охранники-условия и разбор по конструктору (см. «Типы»), опциональный
предмет (в родительном). Ветви — по строке, первая подходящая побеждает. иначе
опционально: блок с иначе им и закрывается, а без него — пустой строкой
или концом кода; если ни одна ветвь не подошла — ошибка исполнения.
назвать число -- это выбор числа: 0, то "ноль" если число чётно, то "чётное" иначе "нечётное"
Длинную ветвь можно переносить по строкам: строка продолжается, если кончается
союзом/оператором/то или следующая начинается с и/или — без
бесконечных строк вправо.
Записи — свой класс пример №53
Запись — базовый способ описать данные с именованными полями. Это возможность самого
языка: Java для неё не нужна (пример №53 работает сам по себе). Под капотом рождается настоящий
именованный JVM-класс с полями, конструктором и геттерами/сеттерами — он же служит фундаментом под
сущности Spring/Jmix, когда до них дойдёт дело. Объявление — блок (как «тип»), кончается пустой
строкой; поле — имя — тип, где тип из ядра
(строка, число, логическое):
запись «Владелец»: имя — строка возраст — число владелец -- это создать «Владелец» задай владельцу имя "Иван" напечатай (имя владельца) прим Иван
Читают поле в родительном ((имя владельца)), пишут — глаголом
задай (совершенный вид, эффект). Работает на обоих бэкендах. Как пометить запись
Java-аннотациями и связать со Spring/Jmix — ниже, в разделе про интеграцию с Java.
Типы данных (АТД) пример №25
Свой тип — сумма вариантов: тип перечисляет конструкторы, по одному на
строку, блок закрывает пустая строка. Вариант — конструктор (поле-тип, …) или голый
конструктор (перечисление). Тип рекурсивен (поле того же типа). Значение строится вызовом
конструктора двумя равноправными формами: скобочно-запятой, как в объявлении
(узел(лист(1), лист(2))), или аппликацией через пробел
(узел (лист 1) (лист 2)); голый конструктор — сразу значение.
тип дерево: лист (число) узел (дерево, дерево) дер -- это узел(лист(1), лист(2)) то же, что: узел (лист 1) (лист 2)
Разбирают значение только через «выбор» — ветвь по конструктору связывает поля с именами (деструктуризация), как record-паттерны в Java 21:
сложить дерево -- это выбор дерева: лист (значение), то значение узел (л, п), то (сложить л) плюс (сложить п) напечатай (сложить дер) прим 3
Ближайший аналог в Java — sealed interface + record: конструктор с полями
≈ класс (много значений), голый конструктор ≈ enum-константа (одно значение). Пока отложено: именованные
поля с прямым доступом, вложенные паттерны. Статическая типизация конструкторов и проверка
исчерпаемости — готовы, см. следующий раздел и раздел «Типы: подсказки и строгость».
Полнота разбора (проверка исчерпаемости) пример №131
выбор без иначе обязан разобрать КАЖДЫЙ вариант типа — забыть
случай нельзя (гарантия в духе Haskell/SPARK). Если разобраны не все варианты и иначе
не написан, проверка типов даёт замечание со списком пропущенных вариантов — по именам,
а не абстрактно «не все случаи»:
тип фигура: круг (радиус) квадрат (сторона) площадь -- это выбор (круг 5): круг (радиус), то 3 умножить на радиус умножить на радиус прим забыли ветвь «квадрат», иначе не написан
… проверка сообщит: разбор «фигура» неполон: не разобран(ы) квадрат. При
строгости свободная/обычная это предупреждение — программа всё
равно запускается; при строгая — блокирующая ошибка, программа не стартует.
Работает и для встроенных типов. «может быть» (нечто/ничего) и
«итог» (успех/сбой, см. раздел «Обработка ошибок») — тоже
АДТ, только встроены в язык, а не объявлены программой через тип …. Полнота видит их
точно так же — забытую ветвь сбой (самый частый баг обработки ошибок: разобрали
успех, забыли провал) поймает та же проверка:
исход -- это выбор (успех 42): успех (значение), то значение прим забыли «сбой»: разбор «итог» неполон: не разобран(ы) сбой
Честные границы. Проверка срабатывает, только когда тип разбираемого значения
известен выводом (прямая конструкция вроде круг 5 или явная аннотация
типа) — если тип не выведен, проверка молчит (без ложных срабатываний), это честная
граница, не баг. Проверяются только варианты-конструкторы; типы и арность ПОЛЕЙ
конструктора, вложенные паттерны и guard-ветви (если …, то … внутри выбор)
в эту проверку не входят — отдельный, ещё не начатый срез.
Система типов: одна ось — от котика до микроконтроллера
Прежде чем нырять в детали — целостная картина. У Глагола одна система типов, растянутая по одной оси — строгости. Это не «динамический язык против статического», а плавный пандус от нуля церемоний до строгих гарантий:
- Свободная (котик в песочнице, дети): типы не пишешь вообще — язык выводит сам, а на противоречие лишь мягко предупреждает, дав программе доиграть.
- Обычная: то же удобство, но противоречие объявленному типу (или выход за диапазон) уже блокирует запуск — ошибка ловится до исполнения.
- Строгая (реальное ПО, микроконтроллер): у глаголов размечены сигнатуры (вход и результат), тело по-прежнему выводится; включаются генерики, полнота разбора и мономорфизация — типы доезжают до голого железа эффективным кодом без обёрток.
Несколько несущих принципов, на которых всё держится:
- Типы — на границе глагола (сигнатура), тело выводится. Правило простое и предсказуемое (как в Rust, Go, Kotlin) — не «пиши везде» и не «полный вывод без аннотаций».
- Незнание ≠ конфликт. «Пока тип не выведен» — законно и молчит (и может стать генериком).
«Типы несовместимы» (ветви
еслиразного типа) — всегда ошибка. - Числа — это ярусы-подтипы (целое ⊂ дробь ⊂ …), а не переменные: «любое число» — это
Число, расширение вверх безопасно. - Полиморфизм «для любого типа» выводится сам (свободная/обычная), а под строгой пишется явно генерик-сигнатурой (переменная типа — одиночная буква Т).
- Мономорфизация: генерик под микроконтроллер специализируется под каждый конкретный тип — абстракция без накладных расходов, проверено прогоном на эмуляторе Cortex-M.
Ниже — то же по частям: подсказки и строгость, ярусы чисел, диапазоны, именованные типы, сигнатуры, генерики. Один язык обслуживает и школьника, и системного инженера, не заставляя первого платить за строгость, нужную второму.
Типы: подсказки и строгость пример №68
Типы в «Глаголе» необязательны. Не написал — язык выведет тип сам (что 5 —
число, а "привет" — строка) и промолчит, пока не заметит явного противоречия. Хочешь
подсказать явно — припиши тип словом типа:
ответ типа Число -- это удвоить 21 удвоить число типа Число -- это число умножить на 2, гарантирует результат типа Число
У глагола можно указать и тип входа (число типа Число), и тип результата
(гарантирует результат типа Число) — оба по желанию. Составные типы пишутся
родительным падежом, как по-русски: список чисел, множество имён,
отображение из имени в владельца.
Ярусы числа. Число — общая аннотация «какое-то число, ярус не закреплён»,
но под ней различаются четыре яруса точности: целое ⊂ десятичное ⊂
дробь, а сверху — неточное плавающее. Литерал сам определяет свой ярус:
5 — целое; точное деление (/, разделить на) —
всегда дробь (даже 10 / 2 — дробь, просто без остатка). Ярус можно
расширить только вверх по решётке: значение типа целое годится и там, где ждут
дробь, но не наоборот — типа целое у дробного значения поймает
несоответствие. Аннотация типа целое/типа дробь/типа плавающее
проверяется по тем же правилам строгости, что и обычные типы. Важная честность: десятичный
литерал вроде 19.99 на деле даёт ярус дробь (точная дробь 1999/100), а не
десятичное — язык хранит десятичные литералы как точные дроби, а не как значения
фиксированного масштаба. Ярус десятичное (масштаб хранится: 10.00 и
10 различимы) сейчас получают только через деньги X/округли X до N
знаков — денежную арифметику; вывод статического типа из этих источников (чтобы
деньги X сама размечалась как десятичное) — следующий шаг, ещё не сделан.
Пример 68 показывает ярусы на рабочих аннотациях.
Диапазоны. Ярус целое можно сузить до конкретных границ:
типа целое от N до M — целое число, ограниченное отрезком [N, M] (обе границы
включительно, границы — целые). Диапазон — подтип целое: он годится всюду, где ждут
просто целое или более широкий ярус, но не наоборот. Если связываемый литерал
выходит за границы, это ловится статически — до запуска программы, ещё на разборе:
высота типа целое от 0 до 40000 -- это 30000 прим внутри границ — чисто напечатай высота прим 30000
Напиши вместо 30000 число 50000 — и разбор сообщит о
замечании ещё до запуска (при строгости обычная и строгая это ошибка —
программа не запускается; при свободная — предупреждение, но программа бежит), не
дожидаясь падения в рантайме. Честно про
границы: сейчас так проверяются только литералы прямо в связывании — выражение
(a плюс b) или значение, пришедшее снаружи (аргумент, ввод, JPA-поле), диапазон пока
не проверяет ни статически, ни во время исполнения; рантайм-проверка внешних значений — следующий
срез работы. Пример 68 показывает и ярусы, и диапазон рядом.
Именованный тип: своё имя ограничению пример №134
Диапазон (да и любой тип) можно назвать один раз и применять к разным величинам — через ту
же форму тип X:, что и алгебраические типы, но с типом на той же строке, что
двоеточие (а не вариантами с отступом):
тип оценка: целое от 2 до 5 математика типа оценка -- это 5 история типа оценка -- это 4
Границы и смысл теперь заданы в одном месте: поменять разом, а по имени сразу видно, что это
оценка, а не просто число. Проверка работает по имени так же, как встроенная —
математика типа оценка -- это 6 заранее сообщит «вне диапазона 2..5». Именем можно назвать
не только диапазон, но и базовый тип (тип идентификатор: строка) или контейнер
(тип имена: список строк).
Чем отличается от запись. запись/сущность
порождает настоящий Java-класс (для базы данных, Spring) — это «переходник в Java».
тип же не порождает никакого класса: это чистое имя для смысла, живущее только внутри
«Глагола». Поэтому имя для диапазона — это тип, а не запись.
Строгость проверки настраивается и говорит, насколько сурово спрашивать за несоответствие
типов: свободная — только предупреждения, программа всё равно запускается (так по
умолчанию, чтобы новичку ничего не мешало); обычная — противоречие объявленному
типу или выход за объявленный диапазон становится ошибкой, программа не запускается; строгая
— то же плюс дополнительные гарантии (размеченные сигнатуры глаголов, полнота разбора, распаковка под
«железо», где без точных типов не собрать быстрый результат). Уровень задаётся на модуле — библиотека «Азбука» строгость: строгая: — либо на
отдельном глаголе, клаузой после тела (рядом с требует/гарантирует):
… -- это …, строгость строгая. Действует правило «ближе к делу — важнее»: пометка на
глаголе перекрывает модуль, а модуль — общую настройку.
Значения, пришедшие из Java-мира, получают тип внешнее — честная отметка
«здесь наш вывод типов заканчивается»: язык не выдумывает про них лишнего. Пока проверка работает
локально (в пределах глагола и связываний); сквозной вывод по всей программе и оптимизации под
«железо» появятся, когда дойдём до соответствующих бэкендов.
Сигнатуры под строгостью: размечаешь границы, не тело пример №136
На ярусе строгая — режиме реального ПО — у каждого глагола размечена
сигнатура: тип каждого параметра и тип результата. А тело выводится само: внутри
ничего размечать не нужно.
площадь ширины типа целое и высоты типа целое -- это ширина умножить на высоту, гарантирует результат типа целое
Убери типа целое у параметра или строку гарантирует … результат — и под
строгостью получишь ✗ с точным указанием, где дописать тип. Сдвинь ползунок на
свободную — сигнатур никто не требует, вывод молча заполнит: это пандус для набросков и
для детей, а не проектный центр.
Правило простое и предсказуемое — типы на сигнатуре (так требуют Rust, Go, Kotlin, TypeScript; в Haskell это соглашение). Всегда знаешь, где ставить тип: на границе глагола, не внутри. Это даёт сразу несколько выгод реального кода:
- Локальные, внятные ошибки. Сигнатура закрепляет границы — несоответствие ловится на границе, а не «где-то в трёх глаголах отсюда».
- Документация прямо в коде. По сигнатуре виден контракт глагола, не читая тело.
- Масштаб. Тело выводится, глядя лишь на параметры и сигнатуры соседей — большая программа проверяется по частям, а не одним исполинским выводом. Размер тела инференсу не страшен.
- Производительность на «железе». Размеченная сигнатура + вывод тела → специализация в
эффективный код без обёрток (
longв регистрах под МК). Типы доезжают до голого железа — ради скорости и надёжности на скудном железе, а не ради красоты теории.
Тело большого и сложного глагола выводится тем надёжнее, чем крепче размечены границы: каждое промежуточное выражение получает тип локально — от известных параметров, литералов и сигнатур вызываемых глаголов. Если конкретную промежуточную величину не закрепляет ничто (редкий случай), проверка попросит уточнить именно её — локально и предсказуемо, а не завалит всё.
Генерики: один глагол «для любого типа» пример №137
Иногда глагол работает с любым типом одинаково — вернуть, что дали; взять первый элемент списка чего угодно. Чтобы не дублировать код под число, строку, список отдельно, тип оставляют переменной. Под свободной/обычной это выводится само:
тождество штуки -- это штука прим работает для любого типа: «тождество 5»→5, «тождество "да"»→"да"
Под строгой переменную типа объявляют явно — отдельной строкой-сигнатурой
имя: входы даёт результат. Переменная типа — одиночная заглавная буква
(Т, У); конкретный тип — слово (Число, список).
Повторение буквы связывает: Т … даёт Т значит «вернётся ровно тот тип, что пришёл».
тождество: Т даёт Т тождество штуки -- это штука
Тело внизу — то же самое, что под свободной; сигнатура сверху читается как контракт одной строкой. Ещё примеры сигнатур (буква = переменная, слово = тип):
первый: список Т даёт Т прим первый элемент списка чего угодно пара: Т, У даёт пару Т и У прим две разные переменные — Т и У найди: список Т даёт может быть Т
Важно не путать переменную типа и «любое число». Т (буква) — это что угодно.
А «любое число» — это Число (слово): ярус-супертип, принимающий целое, дробь и т.д. через
решётку ярусов, а не переменная. Буква против слова — и видно, что это.
Разделители в сигнатуре (:, ,, скобки) — это якоря для
беглого чтения: глазу есть за что зацепиться. Все они набираются на русской раскладке, без
переключения. Сигнатуру можно писать и на обычном уровне — ради читаемости, — а обязательна она под
строгой. Символы < > | & из дженериков других языков нам не нужны: переменные —
буквы, «или»-типы — это тип … : с вариантами (алгебраические типы).
Истинность и сравнения
Значения да/нет (синонимы истина/ложь).
Сравнения: равно/=, больше/>,
меньше/<. Равенство согласуется по роду и числу: «сумма
равна 0», «остаток равен 0», «числа равны» — всё это равенство.
Логические связки: и (все истинны), или (хотя бы одно) —
с коротким замыканием, «или» ниже «и» по приоритету. Повторяющийся союз с запятыми:
A, и B, и C. Операнды-вызовы берутся в скобки: (число больше 0) и (число меньше 10).
напечатай (5 больше 3) прим да
Краткие прилагательные-предикаты пример №11
Краткая форма = утверждение свойства → да/нет. не отрицает.
напечатай (10 чётно) прим да напечатай ("" не пусто) прим нет
Есть: пуст (строки), чётно, нечётно,
положительно, отрицательно (числа).
Арифметика пример №10
Символы + − × ÷ // % или слова
плюс/минус/умножить на/разделить на/нацело/остаток от деления на. Приоритеты обычные.
Составной аргумент печати — в скобках (см. «Скобки»).
напечатай (2 + 3 * 4) прим 14 напечатай (2 плюс 3 умножить на 4) прим 14 напечатай (17 / 5) прим 3.4 — точно напечатай (17 // 5) прим 3 — целочисленное напечатай (17 остаток от деления на 5) прим 2
Числа: точность и дроби пример №117
«Глагол» держит точность по умолчанию — как научный калькулятор, а не как обычный
тип int/double. Есть один тип число, а внутри — целое (без предела)
и дробь; язык сам выбирает представление и не теряет точность.
/(разделить на) — ТОЧНОЕ деление → дробь:1/3=⅓,1/2 + 1/2=1,6/2=3. Никакого молчаливого усечения.//(нацело) — целочисленное (округление к нулю):7 // 2=3. Пара к%(остаток от деления).- Десятичные литералы точны:
0.1 + 0.2=0.3 ровно (а не 0.30000000000000004),19.99— как есть; деньги без потерь. - Целые — без предела: факториалы и степени растут сами, переполнения нет.
- Показ: ходовые дроби — значком (½ ⅓ ¾), с конечной десятичной — десятичной (0.3, 19.99),
прочие —
n/d(5/7). Хранится всё ASCII-каноном.
напечатай (1 / 3) прим ⅓ напечатай (0.1 плюс 0.2) прим 0.3 — ровно напечатай (7 // 2) прим 3 (целочисленное)
Плавающее: корни и тригонометрия пример №118
Для науки и скорости есть приближённые вычисления (double, IEEE-754). Они —
по ЯВНОМУ запросу, чтобы «точность по умолчанию» не размывалась.
- Опт-ин —
приблизительно X: точное значение → неточное double.приблизительно (1/3)= 0.3333333333333333. - Функции дают double сами:
корень/√/sqrt,синус/косинус/тангенс(+ арк-, синонимыsin/cos/tan),логарифм(натуральный,ln),экспонента(exp). Тригонометрия — в радианах. Константыпи/π,е. - Заражение (правило Scheme): где есть неточный операнд — результат неточный
(
приблизительно 0.5 плюс 2= 2.5). Точное ⊕ точное остаётся точным. - IEEE-семантика:
приблизительно 1 / 0= ∞,0/0= NaN (а ТОЧНОЕ1/0— по-прежнему ошибка; у ⅓ нет бесконечности). - Показ: целозначный double несёт
.0(корень 4= 2.0) — видно, что неточное.
напечатай (корень 2) прим 1.4142135623730951 напечатай (синус (пи / 2)) прим 1.0 напечатай (приблизительно (1 / 3)) прим 0.3333333333333333
Деньги: десятичное и округление пример №119
Для ERP — деньги без потерь. «Десятичное» — точное число с ЗАПОМНЕННЫМ масштабом (число знаков
после запятой) и управляемым округлением; под капотом — Java-BigDecimal, так что в поля
базы (JPA/Jmix) ложится один-в-один.
- Сахар
деньги X=округли X до 2 знаков(валютное умолчание):деньги 1000= 1000.00. Масштаб хранится — хвостовые нули не срезаются. - Примитив
округли X до N знаков [режим]— полное управление точностью: любой масштаб, режим по умолчанию коммерческий (0.5 вверх), иначебанковски(к чётному),вниз,вверх,точно(ошибка, если округление потребовалось). - Точность по умолчанию цела (принцип «точно когда можно»): деление считает ТОЧНО, когда
делится (
деньги 6 / 3= 2.00), и округляет ТОЛЬКО бесконечное (19.99 / 3= 6.66). - Заражение: десятичное ⊕ десятичное/целое → десятичное; ⊕ дробь → точная дробь; ⊕ плавающее → плавающее.
напечатай (деньги 1000) прим 1000.00 напечатай ((деньги 19.99) разделить на 3) прим 6.66 напечатай (округли 2.5 до 0 знаков банковски) прим 2
Высокая точность: √, π, e пример №120
Для науки — √n, π, e до любого числа верных знаков (обычное корень/пи/е
дают быстрый double ~15 знаков; здесь — хоть тысяча). Форма — та же до N знаков, что
у округли; результат — десятичное.
корень X до N знаков,пи до N знаков,е до N знаков→ десятичное с N верными знаками (алгоритмы произвольной точности: Ньютон для √, ряды для e, формула Мэчина для π — детерминированы, одинаковы на всех бэкендах).- Честно о границах: это МАТЕРИАЛИЗАЦИЯ до N знаков (сразу десятичное), НЕ символьная математика.
Носить
√2как объект и решать√2·√2 = 2точно язык не умеет (это CAS — не сюда).
напечатай (корень 2 до 30 знаков) прим 1.414213562373095048801688724210 напечатай (пи до 30 знаков) прим 3.141592653589793238462643383280
Алгебраические структуры пример №121 №122
Новый математический тип — комплексные, кватернионы, матрицы, поля Галуа — не «вшивают» в ядро.
Вместо этого автор объявляет алгебраическую структуру (по Винбергу): даёт базовые операции, а
операторы + − × ÷ над её значениями выводятся. Ядро несёт один механизм — три
алгебраических класса:
| Класс | Автор даёт | Операторы |
|---|---|---|
кольцо | сложение, умножение, противоположное, ноль, единица | + − × (÷ запрещён: «в кольце нет деления») |
поле | + ещё обратное | + − × ÷ |
тело | как поле, но × некоммутативно | + − × ÷ (кватернионы) |
Блок <класс> <Имя>: <поля> объявляет структуру; операции — обычные глаголы
с отступом. Конструктор Имя a и b строит значение, имя поля читает компонент
(вещ z). Смешение с обычным числом — через операцию вложение (число
«вкладывается» в структуру). Показ и равенство — свои (показать/равно) или по
умолчанию (покомпонентно).
поле Комплексное: вещ, мним сложение первого и второго -- это Комплексное (вещ первого + вещ второго) и (мним первого + мним второго) ноль -- это Комплексное 0 и 0 единица -- это Комплексное 1 и 0 вложение числа -- это Комплексное число и 0 i -- это Комплексное 0 и 1 прим мнимая единица напечатай ((3 + 4 * i) * (1 + 2 * i)) прим -5+10i
Объявив константу i = 0+1i, дальше пишем числа как математики:
3 + 4 * i, а не Комплексное 3 и 4 — обычное число само вкладывается в
структуру. (Слитный литерал 3+4i — задел на будущее.) Конечное поле GF(5) (пример №122) — та же машина: сюрприз в том, что «половина единицы» равна
3, ведь 2 × 3 = 6 ≡ 1 (mod 5). Обратное считается по малой теореме Ферма.
Кватернионы пример №123 — экземпляр класса тело: деление есть, но умножение
некоммутативно (i·j = k, а j·i = −k). Это ровно то, ради чего в ядре
отдельный класс «тело»: механизм не опирается на переставимость ×, а ÷ выводится
как правое деление a × обратное b. Автор пишет своё умножение (формулу Гамильтона).
Матрицы 2×2 пример №125 — экземпляр класса кольцо, канонический пример именно кольца:
+ − × есть, а ÷ запрещён. Умножение некоммутативно
(A×B ≠ B×A — поворот и отражение не переставить местами), и деления нет, ведь не всякая
матрица обратима: определитель может быть 0. Данные — вложенный список, строится скобочным
литералом матрица ([[0, -1], [1, 0]]); определитель — обычный глагол поверх структуры.
Показ — красивый 2D (операция показать зовёт встроенный рисунок): высокие
скобки и выровненные колонки — напечатай матрица печатает
⎡ 0 −1 ⎤ / ⎣ 1 0 ⎦.
Полноразмерные матрицы n×m — библиотекой пример №127: матрица там — кольцо над вложенным списком (данные строим
скобочным литералом [[0, -1], [1, 0]]), а + − ×, транспонирование,
определитель и доступ к элементу — функции библиотеки, которую подключают. Матрица —
это по сути многомерный массив: скобочный литерал и делает его запись естественной.
- Поставка библиотекой. Структуры кладут в
библиотека «Имя»: …иподключают— тогда новые типы приходят из библиотеки, а не правкой ядра (пример №124: комплексные и кватернионы из одной библиотеки). - Честно о границах: это структуры с операторами, НЕ символьная алгебра (CAS). Законы (ассоциативность и т.п.) мы только объявляем классом, не проверяем. Комплексные/GF(5) — обычные библиотеки над одним ядровым механизмом, а не «рунги» числовой башни.
Скобки пример №16
Правило одно: применение глагола крепче операторов, поэтому составной аргумент
(арифметика, вызов, сравнение) берётся в скобки — «(вычисли это как одно значение)».
Зато между вызовами внешние скобки не нужны: удвоить 3 плюс удвоить 4 — это сумма
двух вызовов.
напечатай (длина ряда) прим вызов как аргумент напечатай ((2 плюс 3) умножить на 4) прим 20 — внутренние скобки меняют порядок
Переносы длинных строк пример №34
Строк «вправо до бесконечности» быть не должно. Длинную строку переносят: строка
продолжается на следующей, если ещё не закончена — висит оператор
(плюс, и, +), предлог (к, с,
из, от, на), то или запятая; или
следующая строка начинается с оператора/предлога/союза.
сумма -- это 1 плюс 2 плюс 3 плюс 4 прим висит «плюс» годно -- это (10 больше 0) и (10 чётно) прим ведёт «и»
Роли по падежу и предлогу пример №12
Роли: объект (вин.), инструмент (твор. / «с»), источник («из/от» + род.), адресат («к» + дат.). Так различаются несколько аргументов — например, два числа в НОД.
сократить число делителем -- это если делитель равно 0, то число, иначе сократить делитель с (число остаток от деления на делитель) нод -- это сократить 48 с 18 напечатай нод прим 6 (алгоритм Евклида)
Рекурсия = «цикл» пример №13
Ядро функциональное и неизменяемое — императивных циклов нет; повтор выражается рекурсией.
свернуть число -- это если число меньше 10, то число, иначе (число остаток от деления на 10) плюс свернуть (число нацело 10) сумма -- это свернуть 1234 напечатай сумма прим 10 (сумма цифр)
Хвостовая рекурсия безопасна на любой глубине (пример №115).
Если само-вызов стоит ПОСЛЕДНИМ действием (в «хвосте» — в `если` или `выбор`, как в аккумулятор-стиле
сумма-до числа с итогом), движок превращает рекурсию в ЦИКЛ: стек не растёт, глубина любая
(как tailrec в Kotlin/Scala). Так «рекурсия с аккумулятором» полноценно заменяет счётный цикл.
Оговорка: спасается только ХВОСТОВАЯ рекурсия. Наивная не-хвостовая
(число × факториал(…)) и глубокий обход дерева по-прежнему ограничены стеком.
Ввод-вывод пример №23
Вывод — напечатай. Чтение со ввода: прочитай строку /
прочитай число, результат — жадно через запомни как. На площадке строки
ввода набираются заранее в панели «Ввод»; программа читает их по порядку.
напечатай "Как тебя зовут?" прочитай строку, запомни как имя напечатай (соедини "Привет, " с имя) прим ввод: Маша → Привет, Маша
Родные тесты пример №17
Тест — часть языка. Инлайн проверь: … должно …; матчеры: равняться,
быть больше/меньше, быть чётным/пустым/… (творительный), вызвать ошибку.
Прогон — кнопкой «✓ Проверить» (команда /проверь). Провал не рушит программу;
ниже — покрытие глаголов: сколько твоих глаголов задеты проверками.
удвоить число -- это число умножить на 2 проверь: удвоить 21 должно равняться 42 прим ✓
Data-driven — таблица случаев, по вердикту на строку:
проверка «удвоение»: удвоить число должно равняться итогу, где: число | итог 3 | 6 21 | 42
Тестовые двойники (дублёры) пример №128
Для тестов ЭФФЕКТНОГО кода (зависит от репозитория/службы) — тест-двойники (по духу Spock,
но родной формой). заглушка [«Тип»] как имя создаёт двойник; пусть <двойник>
«метод» <арг…> → значение стабит ответ (незастабленный зов → ничего); а
проверь: <двойник> получил «метод» <арг…> [ровно N раз] / НЕ получил
проверяет ВЗАИМОДЕЙСТВИЯ. Метафора: двойник получает зов и стрелкой → отвечает. Зависимость
внедряют обычным аргументом. любое — матчер «любой аргумент». Тестовые двойники — интерпретатор-only (тест-время).
заглушка «Хранилище» как склад пусть склад «найти» 7 → нечто("Пётр") прим стаб: зов → ответ напечатай (у склад вызвать «найти» 7) прим нечто(Пётр) проверь: склад получил «найти» 7 ровно 1 раз прим verify
Два вида двойника: заглушка (= Spock Mock) — полностью поддельный; и
шпион <объект> как имя (= Spock Spy) — вокруг реального объекта:
незастабленный зов идёт в НАСТОЯЩИЙ (и пишется), а стаб пусть его переопределяет.
шпион (создать «java.util.ArrayList») как корзина у корзина вызвать «add» "яблоко" прим реальный add проверь: корзина получил «add» "яблоко" прим и записано
Соответствия Spock: заглушка ↔ Mock(), шпион ↔ Spy(),
пусть … → ↔ >>, получил ↔ 1 * mock.m().
Тестирование ВСТРОЕНО В ЯЗЫК, а не прикручено библиотекой (Mockito/Spock — библиотеки).
Проверка свойств (property-based) пример №129
Обычная проверка задаёт КОНКРЕТНЫЙ вход; свойство формулирует ЗАКОН для любого входа, а движок
сам генерирует сотню случайных примеров и ищет контрпример. Форма: свойство «имя»: для любого
<генератор> <имя> [и любого …], <предикат>. Квантор любого — тот же
корень, что матчер любое. Генераторы: целое / целого от A до B,
логическое, дробь, плавающее / плавающего от A до B,
строка / строки до N, список <элемента> (напр. список целых).
Работает на трёх бэкендах (фиксированный сид → одинаковые прогоны).
свойство «сложение целых коммутативно»: для любого целого а и любого целого б, а плюс б равно б плюс а
Найдя нарушение, движок сжимает контрпример до минимального. Например для ложного
для любого целого а, а меньше 500 отчёт покажет не случайное большое число, а точную границу:
✗ свойство «…»: контрпример а=500 (сид …; сжато за 4 шага)
Свои генераторы объявляют так же, как проверки: генератор <имя>: <выражение-генератора>.
Комбинаторы: выбор из [a, b, c] (перечисление), пара из (X) и (Y) (дуалис),
<конструктор> из (X) (применить конструктор к образцам). Сжатие структурное.
генератор цвет: выбор из ["красный", "зелёный", "синий"] свойство «цвет — всегда один из трёх»: для любого цвета ц, (ц равно "красный") или ((ц равно "зелёный") или (ц равно "синий"))
Тонкая настройка: опции (N прогонов) и (сид S) после имени; при условии
<предикат> отбрасывает негодные входы (движок берёт следующий, а сжатие их уважает); отметь
«метка», когда <предикат> считает долю входов с меткой и печатает распределение.
свойство «деление обратно умножению» (200 прогонов): для любого целого от 1 до 9 а и любого целого от 1 до 9 б, при условии б не равно 0, отметь «б большое», когда б больше 5, (а умножить на б) нацело б равно а
→ держится (200 прогонов; 45% «б большое»). Аналог QuickCheck/ScalaCheck, но родной
формой и встроенный в язык. Честно: это ПОИСК контрпримеров на случайных входах, а не доказательство —
отсутствие контрпримера закон не гарантирует.
Параллелизм — данный (по данным) пример №130
Наречие параллельно перед отобрази/отфильтруй над коллекцией.
Результат ТОТ ЖЕ, что последовательно; на JVM-компиляторе — реально по ядрам, на интерпретаторе и в
браузере — последовательно. Безопасно ИЗ-ЗА ЧИСТОТЫ: функции чисты, данные неизменяемы → гонок
данных нет по построению, замки не нужны.
удвоенные -- это параллельно отобрази удвой числа крупные -- это параллельно отфильтруй большой числа
Порядок сохраняется (параллель == последовательность для чистых функций). Свёртка для
параллели должна быть ассоциативной. Честно: ускорение = число ядер ЦЕЛИ; на одноядерных (браузер) — тот
же результат, но без ускорения. Общая память и замки — НЕ в языке (только через внешнее на JVM).
Структурная конкурентность — блок параллельно: запускает НЕЗАВИСИМЫЕ задачи разом и
ждёт ВСЕХ; имена доступны после блока (задачи чисты, без ввода-вывода). Общее время = самой долгой задачи,
а не сумма.
параллельно: отчёт -- это медленныйА данных, прогноз -- это медленныйБ истории напечатай отчёт прим сюда попадём, когда ОБЕ готовы
На JVM-компиляторе задачи бегут конкурентно (виртуальные потоки); на интерпретаторе и в браузере — последовательно (тот же результат). Ошибка — первой по порядку задачи (детерминированно). После блока — пустая строка.
Обещание — поручи <выражение> пускает задачу и сразу отдаёт управление (результат
посчитается отдельно); дождись <обещание> забирает результат (ошибка задачи всплывает тут).
обещанное -- это поручи медленный данных … другая работа … итог -- это дождись обещанное
Дальше по дорожной карте: каналы, акторы и реактивные события.
Два режима конца предложения пример
По умолчанию построчно (строка = предложение). Директива далее прозой —
предложение кончается точкой, перевод строки незначим; далее построчно возвращает.
Полный полигон — сортировка вставками (№15) и оба богатых примера, где всё сведено вместе.
Таблица примеров
Номер — живая ссылка: открывает площадку с загруженным и запущенным примером (по нарастанию сложности; богатые — последними).
| № | Пример | Что показывает |
|---|---|---|
| 1 | Привет | печать строки |
| 2 | Связывание | сущность: имя = значение |
| 30 | Имена | смысловые имена; дефис, ёлочки-аббревиатуры, индекс x_1; цифра к слову — нельзя |
| 73 | Многословные имена | буква-индекс «Пункт А» (голый) и имя-фраза «первое слагаемое» в ёлочках |
| 3 | Объединение | соединение строк, предлог «с» |
| 4 | Порядок | роль по падежу — свободный порядок слов |
| 5 | Список | обобщающее «всё:» |
| 6 | Строго/лениво | вид глагола: когда вычислять |
| 7 | Ленивость | наблюдаемая ленивость связывания |
| 8 | Свой глагол | жадный/ленивый глагол, параметры по падежу |
| 9 | Условия | если … то … иначе, сравнения |
| 10 | Арифметика | символы и слова; рекурсия (сумма 1..N) |
| 11 | Предикаты | краткие прилагательные, «не» |
| 12 | Роли (НОД) | многоаргументный глагол — алгоритм Евклида |
| 13 | Рекурсия | «цикл» функционально: глагол зовёт себя |
| 16 | Скобки | все случаи группировки — растёт с языком |
| 24 | Выбор (case) | многоветочный «выбор»: сопоставление + охранники |
| 25 | Типы данных (АТД) | свой тип: суммы вариантов, конструкторы, разбор по конструктору |
| 34 | Переносы строк | перенос длинной строки по незавершённости |
| 14 | Списки | список-значение, операции в родительном падеже |
| 15 | Сортировка (вставками) | сортировка вставками — чисто функционально |
| 31 | Пузырёк | сортировка пузырьком — проходы над неизменяемым списком |
| 32 | Поиск | линейный и двоичный поиск (с честной оговоркой про O(n)-индекс) |
| 17 | Тесты | родные проверки, data-driven, покрытие глаголов |
| 18 | Палиндром (#9) | LeetCode: разворот цифр, рекурсия |
| 19 | Степень двойки (#231) | LeetCode: рекурсия, остаток |
| 20 | Fizz Buzz (#412) | LeetCode: условия + строки, data-driven тест |
| 21 | Лестница (#70) | LeetCode: числа Фибоначчи |
| 22 | Максимум списка | LeetCode: рекурсия по хвосту |
| 26 | Пути в сетке (#62) | LeetCode Medium: рекурсия по двум аргументам |
| 27 | Дерево поиска (#98) | LeetCode Medium: разбор типа с границами |
| 28 | Степень (#50) | LeetCode Medium: быстрое возведение O(log n) |
| 29 | Размен монет (#322) | LeetCode Medium: минимум монет, рекурсия |
| 33 | Расшифровка (#91) | LeetCode Medium: Decode Ways, строковые операции |
| 35 | Edit Distance (#72) | LeetCode Hard: расстояние редактирования, минимум из трёх |
| 36 | Регулярка (#10) | LeetCode Hard: сопоставление с «.»/«*» |
| 37 | Шаблон (#44) | LeetCode Hard: wildcard-сопоставление «?»/«*» |
| 38 | Чередование (#97) | LeetCode Hard: interleaving двух строк |
| 39 | Подпоследовательности (#115) | LeetCode Hard: число различных подпоследовательностей |
| 23 | Ввод-вывод | чтение со ввода, панель «Ввод» |
| ★ | начинающий / профессионал | всё вместе, в двух стилях |
Среда (площадка)
- Пиши код слева — подсветка синтаксиса и номера строк как в IDE.
- Запусти: кнопка «▶ Запустить» или
Ctrl/Cmd + Enter. Вывод — справа. - Примеры: кнопки сверху загружают готовый код (по нарастанию сложности).
- Ползунок между колонками — тащи влево-вправо, чтобы менять ширину.
- Легенда «Цвета:» под редактором объясняет подсветку.
- Тёмная/светлая тема — по настройке системы.
Модульность — свои подсистемы пример №63
Программу можно делить на именованные единицы с собственной областью видимости — не «папки для файлов», а языковой механизм изоляции. Две роли пространств имён:
- библиотека — переиспользуемая библиотека определений (импортируют как имена);
- раздел — нейтральное пространство имён (часть программы).
Отдельно — служба (пример №64): это объект-сервис (инъектируемый
@Service-бин с методами), конструктивно — класс, как контроллер/приложение,
а не модуль. Службу внедряют (встрой), а не импортируют — этим она и отличается от
статической библиотеки.
библиотека «Арифметика» открывает удвоить, площадь: удвоить число -- это число умножить на 2 вспомогательное число -- это число плюс 1 прим внутреннее — снаружи не видно подключи библиотека «Арифметика» напечатай (удвоить 21) прим 42 — импортировано напрямую напечатай (площадь из «Арифметика» 5) прим 25 — квалифицированный доступ «из»
Видимость. Нет открывает → всё публично (для новичка — просто). Есть
открывает A, B → публично только это, остальное внутреннее. Направленный экспорт
открывает X для «Клиент» открывает X только модулю «Клиент» — так низкоуровневое
прячут от верхних слоёв (вертикальная и горизонтальная изоляция подсистем внутри монолита).
Импорт — подключи библиотека «Имя» (после чего имена доступны напрямую), а при совпадении
имён — квалификация родительным через «из»: площадь из «Арифметика» (голый родительный
остаётся за аргументами). Ацикличность зависимостей — правило компилятора: цикл модулей — ошибка.
Слои (пример №66): модуль может объявить слой Чистой
Архитектуры — сущности → правила → адаптеры → инфраструктура (внутрь→наружу). Действует
Правило Зависимостей: зависеть можно только ВНУТРЬ, к стабильному (внешний слой — от внутреннего,
но не наоборот). библиотека «Ядро» слой сущности: — низкоуровневое не протекает на верхние слои;
нарушение направления — ошибка компиляции. Это вертикальная изоляция сверх направленного экспорта.
Дружелюбно для самых маленьких: модульность полностью опциональна — программа без единого модульного слова работает как раньше, и споткнуться о видимость/ацикличность/слои новичок не может (слои проверяются только у модулей, которые их объявили).
Программирование по договору пример №65
У глагола может быть контракт (Design by Contract, Мейер): требует — предусловие
(проверяется на входе), гарантирует/обеспечивает — постусловие (на выходе;
может ссылаться на результат). Нарушение — ошибка выполнения. Клаузы пишутся после тела
через запятую:
утроить число -- это число умножить на 3, требует: число больше 0 половина числа -- это число разделить на 2, гарантирует: результат меньше число
Договор дополняет модульность и валидацию: открывает говорит что публично,
обязательно/только цифры задают инварианты данных, а договор — как ведёт
себя глагол. Это часть парадигмы «встроенной проверяемости»: контракты — живая исполнимая спецификация.
Проверяет интерпретатор; компилятор их опускает (как отключаемые проверки-утверждения).
Кавычки: имя или строка пример №67
Лапки "…" и ёлочки «…» — это одно и то же (взаимозаменяемы): типографические
варианты одного знака. Человек всегда может вводить лёгкие лапки; ёлочки — каноническая форма (их ставит
показ/агент). Что означает кавычка — имя или строку — решает позиция, а не пара кавычек:
- Позиция значения (голый квот в выражении, без аргументов) → строка:
напечатай "привет"≡напечатай «привет». - Позиция имени (грамматика ждёт имя) → имя:
запись «Точка»:≡запись "Точка":,создать «Точка»,проверка «сумма»:,у «java.util.List» вызвать …, голова определения«общий делитель» -- это …. - Квот + аргументы → вызов функции по этому имени:
«НОД» 12 8.
Вложенность. Ёлочки направленные («≠»), поэтому вкладываются со счётом
глубины: «внешнее «внутреннее»». Лапки симметричны — не вкладываются ("внеш "внутр""
неоднозначно); для кавычек внутри строки используйте экранирование \" или ёлочки снаружи.
Что нельзя (и почему так решено). Сослаться на многословное нульарное имя-значение голым
квотом — нельзя: «общий делитель» в позиции значения станет строкой «общий делитель», а не
значением. Это сознательный размен: правило «в значении → строка» простое и без граблей (опечатка в имени не
превращается тихо в строку через поиск по таблице имён). Обход — однословное имя: осд -- это 12.
Многословные функции (с аргументами) работают: «общий делитель» 12 8.
Каноническая форма — ёлочки; лапки нормализуются в ёлочки (отображение по режимам — клавиатурный/агентский — дорабатывается отдельно). В самом языке обе формы уже полностью равнозначны.
Имена и индексы пример №46
Имя в «Глаголе» — смысловое слово, которое можно склонять (падеж несёт роль). Поэтому
цифру нельзя приклеивать к слову: шаг1, число2 — не имена. Причина
принципиальная: ряд1 не просклонять (ряда1? движок не знает), и падеж-роль
ломается. Это категориальная ошибка, и компилятор отвергает её с подсказкой.
Что использовать вместо этого:
- смысловое слово —
цена,налог,сторона; - порядковое + слово (перечисление) —
первый ряд,второй ряд; склоняется целиком:из первого ряда; - слово + буква-ярлык —
пункт «А»,вершина «Б»; склоняется слово:из пункта «А»; - индекс-символ (формульная нотация) —
x_1,x_2,x_i: основа — один символ (латиница/греческая буква), индекс — цифра или буква, пишется явно через подчёркивание. Это нотация: не склоняется. Подчёркивание делает индекс намеренным (случайно не наберёшь) и подталкивает подумать над человекочитаемым именем. Вид зависит от режима: в клавиатурном — как в файле (x_1), в агентском — подстрочным (x₁,xᵢ); обе формы — одно имя (агентскуюx₁язык тоже принимает).
Индекс к слову (значение_1) тоже нельзя: основа индекса — один символ, а не
слово; для перечисления — порядковое (первое значение).
Коллекции: пара пример №94
Пара — упорядоченная двойка (возрождённый дуалис, §6): две вещи, что ходят вместе.
Это несущий элемент коллекций — запись словаря есть пара «ключ → значение», а ещё пара удобна,
чтобы вернуть два значения разом. Строится сочинительным «и», члены — первое/второе:
точка -- это пара 3 и 8 напечатай точка -- (3, 8) напечатай (первое точки) -- 3
Пары равны структурно — по обоим членам, и порядок важен: пара 1 и 2 ≠
пара 2 и 1. Пара упорядочена (есть «первое» и «второе»); симметрию «оба» дадим отдельно.
Коллекции: множество пример №95
Множество — набор без повторов; порядок обхода = порядок добавления. Литерал —
множество: A, B, C (точно) или все разные: A, B, C (для детей) — синонимы:
набор -- это множество: "спорт", "музыка", "спорт" -- {спорт, музыка} напечатай (содержит "кино" в наборе) -- нет
Операции: содержит X в <множестве>, добавить X в <множество> (неизменяемо,
дубликат не растит), длина, и алгебра множеств — объединение/пересечение/разность
A с B (второе множество через предлог «с»). Равенство множеств не зависит от порядка
(в отличие от списка и пары): множество: 1, 2, 3 = множество: 3, 2, 1.
Коллекции: словарь (map) пример №96
Словарь — пары «ключ → значение» (map); порядок обхода = порядок добавления. Литерал —
стрелка → (или диграф ->):
телефоны -- это словарь: "Иван" → "111", "Мария" → "222" напечатай телефоны["Иван"] -- нечто ("111")
Доступ по ключу — скобки телефоны["Иван"] — возвращает «может быть» (нечто (значение)
или ничего); разбирай через выбор. Ещё: ключи/значения/пары
(как списки), длина, и добавить (пара K и V) в <словарь> (неизменяемо; тот же ключ — замена
значения). Запись словаря «ключ → значение» — это пара. Равенство не зависит от порядка. NB: «словарь» — это
коллекция; модули-пространства имён теперь называются словом библиотека.
Живая задача — частотный словарь (пример №99): словарь-аккумулятор
строим свёрткой по списку слов; для каждого берём текущий счёт (словарь[слово] → «может быть» → 0) и
кладём +1. Здесь словарь читается естественнее ручных параллельных списков.
Коллекции: список — доступ по номеру пример №97
Список — индексируемая последовательность. Доступ по номеру — с 1 (по-человечески, а не с 0;
ноль-смещение остаётся под капотом). Скобки числа[N] и словесно элемент N из чисел —
одно и то же; вне границ → ничего (оба дают «может быть», разбирай выбором):
числа -- это всё: 10, 20, 30 напечатай числа[2] -- нечто (20)
Добавление: добавить X в <список> — в конец (умолчание), добавить X к <списку>
— в начало (цепочка «голова + хвост», §20). Плюс содержит X в <списке>, длина,
первый/хвост. Счёт с 1 согласуется с математикой и школой; 0-смещение живёт лишь на
границе с Java-массивами.
Коллекции: операции — это библиотека на Глаголе пример №98
Как в Java коллекции построены поверх Core, так у нас операции коллекций написаны на самом Глаголе (стандартная библиотека-«прелюдия»), а не встроены в ядро. Ядро несёт лишь неприводимый минимум — две «двери»:
элементы <коллекции>— разобрать множество (а также список/словарь) в список;множество из <списка>— собрать множество из списка (повторы схлопываются).
Поверх них содержит, объединение/пересечение/разность,
ключи/значения — обычные глаголы прелюдии (разобрать → обработать список → собрать).
Значит, свою операцию пишут так же — язык открыт, а не закрыт. Множество квадратов:
квадраты набора -- это множество из (возвести (элементы набора)) возвести список -- это если список пуст, то пустой список, иначе добавить ((первый списка) умножить на (первый списка)) к (возвести (хвост списка)) напечатай (квадраты (множество: 1, 2, 3)) -- {1, 4, 9}
Своё определение с тем же именем затеняет библиотечное. NB: добавить и доступ []
пока остаются примитивами ядра — их перенос требует перегрузки глаголов по ролям (отдельная возможность).
Конвейер именной группой пример №101
Конвейер именной группой — аналог Java Stream API. В Java обработка потока пишется
цепочкой через точки: numbers.stream().filter(even).map(sq).sum(). Точка тут — синтаксический клей, которым язык
компенсирует отсутствие падежа: получатель обязан стоять слева. У нас падеж есть, и он несёт роль сам —
поэтому клей не нужен. Шаги конвейера связывает родительный падеж («чего»), без точек и скобок:
напечатай (сумма квадратов чётных ряда) -- чётные 2,4,6 → квадраты 4,16,36 → сумма 56
Читается слева направо, как по-русски: «сумма квадратов чётных чисел». То же со скобками —
сумма (квадраты (чётные ряда)) — эквивалентно, но многословнее (скобки Лиспа вместо падежа).
Как это понимается. Шаг конвейера — это имя, которое разрешается в глагол, берущий ровно один
аргумент (рамка {объект}). Идущие подряд шаги сворачиваются с ближайшим значением справа налево.
Свёртка применяется, только когда обычное («плоское») прочтение невозможно по рамке — иначе язык ничего не
трогает; а если оба прочтения законны, он не гадает, а просит уточнить скобками. Союз «и» —
перечисление, конвейером не читается (наибольшее числа и делителя — два аргумента).
Терминалы и комбинаторы стандартной библиотеки
Шаги конвейера — обычные глаголы прелюдии. Терминалы сворачивают коллекцию в одно значение:
сумма, количество, наибольшее, наименьшее чисел
(пример №102). Групповые строят словарь:
подсчёт ряда — частотный словарь «элемент → сколько раз» (тот самый частотный словарь —
одной строкой), группировка ряда меркой — «ключ → список подошедших»
(пример №103). Комбинаторы берут функцию
творительным падежом (§ФВП): отобрази/отфильтруй/сверни ряд действием.
Имена терминалов — номиналы (подсчёт, сумма), а не повеления
(посчитай): терминал даёт значение, а значение по-русски именуется существительным
с родительным (функциональная морфология). Оттого и читается: частоты -- это подсчёт слов.
Лидер по мере (argmax/argmin) пример №105
наибольшее/наименьшее чисел берут максимум/минимум коллекции. Но часто нужен
не максимум значения, а элемент, у которого значение наибольшее — по некоторой мере.
Мера — функция творительным падежом (§ФВП), отображающая элемент в число:
размер слова -- это длина слова
наибольшее (всё: "кот", "пёс", "ёжик") размером прим ёжик — самое длинное
наименьшее ряда размером прим самое короткое
Раз сравнивается мера (число), а не сам элемент, это работает и над не-числами — строками,
парами, записями. На этом стоит мода (самое частое): пара частотного словаря с наибольшим счётом, и
берём её ключ. Прелюдия уже свернула это в один номинал — мода ряда:
мода слов прим самое частое слово — одним словом
прим внутри (для понимания) — argmax частотного словаря по счёту:
первое (наибольшее (пары (подсчёт слов)) счётом) где счёт пары -- это второе пары
Это в точности Java Collections.max(coll, comparator) / Stream.max(comparing(…)).
Как и в JDK, конкретные статистики (медиана, дисперсия) в стандарт не зашиты — их собирают из этих
примитивов. Тонкость: перегруженный глагол (например второе) как меру передать нельзя —
нужна неперегруженная обёртка (счёт); встроенные (длина) тоже заворачивают в номинал.
Поток: сортировка, top-N, кванторы, flatMap пример №108
Жадная часть Stream API — обычные глаголы прелюдии над любой коллекцией. Всё возвращает значение, поэтому имена — номиналы, компонуются и идут шагами конвейера:
- Сортировка:
упорядочение ряда(по значению),упорядочение ряда мерой(по мере — ФВП творительным, какнаибольшее … мерой). По убыванию —наоборот. - Ограничение:
первые N ряда;наоборот ряда(обратный порядок). В паре с сортировкой — top-N одной фразой:первые 3 (упорядочение ряда мерой). - Кванторы (да/нет):
любой ряда условием(anyMatch),все ряда условием(allMatch),никакой ряда условием(noneMatch). - Разбиение:
разбиение ряда условием→ пара (подходящие, остальные). - flatMap / flatten:
сцепление ряда действием— каждый элемент разворачивается в список и всё сцепляется; без мерысцепление рядов— просто плющит список списков.
напечатай (упорядочение (всё: 3, 1, 2)) прим → [1, 2, 3] напечатай (первые 2 (наоборот (упорядочение чисел))) прим два наибольших
Ленивость (бесконечные последовательности, ленивый limit) и параллелизм — отдельно,
Фаза 3 (вид глагола = ленивость). Здесь всё жадное: годится для конечных коллекций.
Строки в потоке: слова и мода пример №106
Чтобы пустить в конвейер текст, его надо разбить на слова. Примитив ядра слова строки
режет строку по пробелам в список слов (пустые отбрасывает). Дальше — вся библиотека: частотный анализ
ТЕКСТА читается одной русской фразой, где в Java нужны Scanner + HashMap +
Collectors.groupingBy + max:
текст -- это "кот пёс кот ёж кот"
слова текст прим [кот, пёс, кот, ёж, кот]
подсчёт (слова текст) прим {кот -> 3, пёс -> 1, ёж -> 1}
мода слов текста прим кот — конвейер как ИМЕННАЯ ГРУППА без скобок
слова — полноправный ШАГ конвейера, поэтому частотный анализ читается одной русской
фразой «мода слов текста» (как сумма квадратов чётных ряда), без вложенных скобок.
Обходимость своего типа
Чтобы тип попал под всю библиотеку, в Java его помечают implements Iterable. У нас интерфейс
не нужен: обходимость — это номинал, возвращающий список. Напишите обход своего дерева в список —
и сумма, содержит, конвейер заработают по нему сами
(пример №104):
листва дерева -- это выбор дерева: лист (значение), то всё: значение узел (л, п), то сцепить (листва л) (листва п) напечатай (сумма (листва образца)) -- 6 напечатай (сумма листвы образца) -- 6 — конвейером, без скобок
Перегрузка глаголов по сигнатуре пример №100
Русский сам перегружает глагол предлогом: идти в школу · идти на работу · идти к другу — глагол один, предлог задаёт смысл. «Глагол» это поддерживает: один корень может нести несколько определений, если они различаются падежной рамкой.
положить предмет в короб -- это соединить предмет с " — убрано в короб" положить предмет к стене -- это соединить предмет с " — прислонено к стене" напечатай (положить "лыжи" в "короб") -- лыжи — убрано в короб напечатай (положить в "короб" "санки") -- порядок слов свободен
СИГНАТУРА у нас — это падежная рамка, а НЕ типы. Это важно и отличается от привычного: в
большинстве языков «сигнатура» = типы+арность. У нас на поверхности динамическая типизация, и перегрузы
различает управление — какими предлогами/падежами глагол управляет. Рамка — мультимножество
ролей (роль + кратность), поэтому арность учитывается сама собой: подсчёт слов свод
(объект×2) ≠ подсчёт слов (объект×1).
Как выбирается перегруз (лестница). Рамка вызова сравнивается точно, ступень за ступенью:
из «Модуль» → свой модуль → импортированные → верхний уровень → прелюдия → встроенные.
Не совпало на ступени — идём ниже; не совпало нигде — ошибка вида
глагол «положить»: нет рамки {объект, на}; есть: {объект, в}, {объект, к}.
Ограничения (читать обязательно)
- Перегрузки по типам нет. Различает только падежная рамка.
удвоить числоиудвоить строку— это одна рамка {объект} и, значит, дубль-ошибка. - Совпадение строгое. Ни частичного, ни «наиболее подходящего» подбора — язык не угадывает. Не совпало — честная ошибка со списком доступных рамок.
- Глагол как значение (ФВП) — только при ОДНОМ перегрузе. Голое имя (
отобрази список удвоением) рамки не несёт, поэтому передать перегруженный глагол значением нельзя — будет ошибка «перегружен … как значение передать нельзя». Синтаксиса уточнения рамки в v1 нет. - Мост из метода записи (§4) — тоже только при одном перегрузе: он вызывает позиционно и рамку не различает.
- Встроенные глаголы — нижний слой и в перегрузке по рамке не участвуют: сначала ищется пользовательский/библиотечный перегруз, и лишь потом встроенный.
- Прелюдия — низший приоритет. Своё определение перекрывает библиотечное с той же рамкой;
библиотечные с другими рамками остаются доступны (это может удивить). Явно достать библиотечное
всегда можно:
содержит из «Прелюдия» 2 в набор. - Импортированные модули ищутся раньше верхнего уровня — существующее правило модулей.
- Дубль — это совпадение и корня, и рамки в одном пространстве имён: ошибка «глагол … с рамкой {объект, в} уже определён».
Нативная компиляция под микроконтроллер (стадия 1)
Третий бэкенд — «Глагол» компилируется в C и исполняется на МК (в эмуляторе QEMU; реальное
железо STM32F103 — следующая стадия). В отличие от площадки, этот пример не исполняется в браузере —
нужен локальный тулчейн под целевую архитектуру: arm-none-eabi-gcc + qemu-system-arm
(ARM) или riscv-none-elf-gcc + qemu-system-riscv32 (RISC-V).
Поддержана ВТОРАЯ архитектура — RISC-V (rv32), тем же эмиттером Глагол→C: смена C-компилятора на
riscv-none-elf-gcc (флаги -march=rv32imac -mabi=ilp32) и запуск
qemu-system-riscv32 -M virt -bios none -semihosting. Честно: арх-различие локализовано в ДВУХ
местах — мк/rt.c (semihosting-вызов) и мк/startup.c (стартап: таблица векторов у
ARM / _start у RISC-V) через #if defined(__riscv) … #else … #endif; эмиттер
КомпиляторC и остальная логика рантайма (число→строка и т.д.) — ОБЩИЕ, архитектурно-независимые.
Проверено в эмуляции (qemu-system-riscv32 -M virt). Это разблокирует МК на RISC-V за пределами
эталонной платформы virt — первый такой таргет, ESP32-C3, описан ниже; RP2350-RISC-V —
впереди.
Поддержаны пять ядер — Cortex-M3 (-mcpu=cortex-m3, QEMU-машина
lm3s6965evb), Cortex-M4 (-mcpu=cortex-m4, QEMU-машина mps2-an386),
Cortex-M7 (-mcpu=cortex-m7, QEMU-машина mps2-an500) — все три на общем линкере
мк/cortexm.ld — Cortex-M0/M0+ (-mcpu=cortex-m0plus, QEMU-машина
microbit) на своём линкере мк/microbit.ld (16 КБ RAM), и Cortex-M33
(-mcpu=cortex-m33, QEMU-машина mps2-an505) на своём линкере мк/an505.ld.
Различие ядра/платы —
всего три поля: -mcpu, имя QEMU-машины и линкер; эмиттер C, rt.c и стартап — общие
для всех пяти. M7 не вносит нового: те же флаги, что и у M3/M4, меняется только -mcpu=cortex-m7
и QEMU-машина mps2-an500. Честно: (а) карта памяти РАЗНАЯ по платам — у M3/M4/M7 общий линкер
мк/cortexm.ld,
а у microbit всего 16 КБ RAM, поэтому свой линкер мк/microbit.ld (общий 64-КБ линкер вешает
HardFault); (б) сборка — строго freestanding (-nostdlib -nostartfiles, никакой libc), а
библиотеку libgcc линкуем ТОЧНЫМ путём под конкретное ядро (arm-none-eabi-gcc -mcpu=<ядро>
-print-libgcc-file-name) — она несёт программное деление __aeabi_uidiv, понадобится
для нацело/остатка с непостоянным делителем на Cortex-M0/M0+ (ARMv6-M без аппаратного
деления); (в) microbit — это плата на nRF51
(Cortex-M0), она валидирует ядро/класс M0/M0+ (M0+ инструкционно-совместим с M0), а mps2-an500 — плата
ARM MPS2+ с ядром Cortex-M7, а не конкретный чип вроде STM32F7 с его периферией. Проверено пока в
эмуляции на уровне ядра, не на конкретном чипе с периферией.
Cortex-M33 (ARMv8-M, TrustZone) поддержан на QEMU-машине mps2-an505 (плата MPS2+
с SSE-200). Честно: у этой платы СВОЯ карта памяти — стек и данные должны лежать в писабельном BRAM
0x10000000, поэтому свой линкер мк/an505.ld (единый регион вместо
FLASH/RAM-пары у M3/M4/M7); регион 0x30000000 на mps2-an505 НЕ writable-RAM — стекирование
туда даёт STKERR-фолт (проверено спайком). TrustZone-специфики в стартапе НЕ потребовалось — обычный ARM
Cortex-M стартап. M33 — это ядро RP2350 (Raspberry Pi Pico 2). Проверено в эмуляции.
ESP32-C3 (RISC-V rv32imc) — ПЕРВЫЙ таргет через стек РЕАЛЬНОГО вендора (Espressif), не эталонная
QEMU-платформа. Честно: (а) переиспользование, не форк — тот же эмиттер Глагол→C и та же арх-ветка RISC-V
в мк/rt.c/мк/startup.c, что и у virt выше, эмиттер и стартап НЕ
менялись; (б) отличается ТОЛЬКО плата-слой — компилятор riscv32-esp-elf-gcc (тулчейн Espressif,
не mainline riscv-none-elf-gcc), QEMU-форк Espressif (машина esp32c3, bare-metal
через -kernel — БЕЗ полного образа esp-idf) и линкер мк/esp32c3.ld (код в IRAM
0x40380000, данные/стек в DRAM 0x3FC80000 — своя карта памяти чипа, не платы
virt); (в) Espressif-QEMU при старте печатает в консоль баннер загрузчика (Not
initializing SPI Flash, Loading kernel at address) — это шум эмулятора, не вывод
программы, интеграционный тест отфильтровывает эти строки перед сверкой с интерпретатором. Тулчейн и QEMU —
билды Espressif под Windows, качаются вручную с релизов
github.com/espressif/qemu (QEMU) и
github.com/espressif/crosstool-NG
(компилятор riscv32-esp-elf-gcc). Проверено в эмуляции; следующий шаг — реальная плата
ESP32-C3 (например, ESP32-C3 SuperMini).
Конвейер
.гл → (эмиттер --си) → app.c → (arm-none-eabi-gcc +
мк/rt.c + мк/startup.c + линкер ядра) → app.elf →
(qemu-system-arm -M <машина ядра> -semihosting) → вывод в консоль. Вывод — через ARM
semihosting (та же «печать», что видно в консоли эмулятора).
Команды (Cortex-M3, QEMU lm3s6965evb)
java -jar "build/libs/глагол-all.jar" --си примеры-мк/1-привет.гл -o app.c arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \ -T src/main/resources/мк/cortexm.ld -o app.elf \ src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \ "$(arm-none-eabi-gcc -mcpu=cortex-m3 -print-libgcc-file-name)" qemu-system-arm -M lm3s6965evb -nographic -semihosting -kernel app.elf
Ожидаемый вывод примера примеры-мк/1-привет.гл: Глагол на Cortex-M / 42
/ 142 — построчно, совпадает с интерпретатором.
Команды (Cortex-M4, QEMU mps2-an386)
Та же app.c и тот же линкер — меняются только -mcpu и QEMU-машина:
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \ -T src/main/resources/мк/cortexm.ld -o app.elf \ src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \ "$(arm-none-eabi-gcc -mcpu=cortex-m4 -print-libgcc-file-name)" qemu-system-arm -M mps2-an386 -nographic -semihosting -kernel app.elf
Вывод — тот же, что и на Cortex-M3 (совпадает с интерпретатором).
Команды (Cortex-M7, QEMU mps2-an500)
Та же app.c и тот же линкер cortexm.ld — меняются только -mcpu
и QEMU-машина:
arm-none-eabi-gcc -mcpu=cortex-m7 -mthumb -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \ -T src/main/resources/мк/cortexm.ld -o app.elf \ src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \ "$(arm-none-eabi-gcc -mcpu=cortex-m7 -print-libgcc-file-name)" qemu-system-arm -M mps2-an500 -nographic -semihosting -kernel app.elf
Вывод — тот же, что и на Cortex-M3/M4 (совпадает с интерпретатором).
Команды (Cortex-M0/M0+, QEMU microbit)
Та же app.c — меняются -mcpu, QEMU-машина И линкер (у microbit только
16 КБ RAM, общий с M3/M4/M7 линкер cortexm.ld ей не подходит — вешает HardFault):
arm-none-eabi-gcc -mcpu=cortex-m0plus -mthumb -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \ -T src/main/resources/мк/microbit.ld -o app.elf \ src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \ "$(arm-none-eabi-gcc -mcpu=cortex-m0plus -print-libgcc-file-name)" qemu-system-arm -M microbit -nographic -semihosting -kernel app.elf
Вывод — тот же, что и на Cortex-M3/M4/M7 (совпадает с интерпретатором): именно это сверяет
интеграционный тест СиQemuDiffTest (теперь для всех пяти ядер ARM, включая Cortex-M33 ниже).
Библиотека libgcc (её точный путь
берём arm-none-eabi-gcc -mcpu=<ядро> -print-libgcc-file-name) несёт программное
деление __aeabi_uidiv — понадобится для нацело/остатка с непостоянным
делителем на Cortex-M0/M0+ (ARMv6-M без аппаратного деления); в текущем примере деления нет
(константный делитель свёрнут в умножение), на M3/M4/M7 линковка libgcc безвредна.
Команды (Cortex-M33, QEMU mps2-an505)
Та же app.c — меняются -mcpu, QEMU-машина И линкер (у mps2-an505 свой
единый писабельный BRAM 0x10000000, общий линкер cortexm.ld ей не подходит —
регион 0x30000000, куда он кладёт стек, на этой плате не writable-RAM и даёт STKERR-фолт):
arm-none-eabi-gcc -mcpu=cortex-m33 -mthumb -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \ -T src/main/resources/мк/an505.ld -o app.elf \ src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \ "$(arm-none-eabi-gcc -mcpu=cortex-m33 -print-libgcc-file-name)" qemu-system-arm -M mps2-an505 -nographic -semihosting -kernel app.elf
Вывод — тот же, что и на Cortex-M3/M4/M7/M0 (совпадает с интерпретатором): именно это сверяет
интеграционный тест СиQemuDiffTest для всех пяти ядер ARM. TrustZone-специфики (M33 —
ARMv8-M) в стартапе не потребовалось — обычный ARM Cortex-M стартап.
Команды (RISC-V rv32, QEMU virt) — вторая архитектура
Та же app.c — компилятор, арх-флаги, линкер и QEMU-машина другие (свой линкер
мк/virt-riscv.ld: RAM платы virt начинается с адреса 0x80000000, не
с нуля, как у ARM):
riscv-none-elf-gcc -march=rv32imac -mabi=ilp32 -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \ -T src/main/resources/мк/virt-riscv.ld -o app.elf \ src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \ "$(riscv-none-elf-gcc -march=rv32imac -mabi=ilp32 -print-libgcc-file-name)" qemu-system-riscv32 -M virt -bios none -nographic -semihosting -kernel app.elf
Вывод — тот же, что и на всех пяти ядрах ARM (совпадает с интерпретатором): интеграционный тест
СиQemuDiffTest прогоняет его шестым таргетом в общем списке (арх-агностичный харнесс: описание
таргета — данные, компилятор/флаги/линкер/QEMU резолвятся по нему, а не жёстко в коде теста).
Команды (ESP32-C3, Espressif QEMU esp32c3) — реальный вендор-чип
Та же app.c и та же арх-ветка RISC-V в rt.c/startup.c — меняются
компилятор (Espressif-тулчейн), линкер (карта памяти чипа) и QEMU (Espressif-форк с машиной
esp32c3):
riscv32-esp-elf-gcc -march=rv32imc -mabi=ilp32 -nostdlib -nostartfiles -ffreestanding -finput-charset=UTF-8 -O2 \ -T src/main/resources/мк/esp32c3.ld -o app.elf \ src/main/resources/мк/startup.c src/main/resources/мк/rt.c app.c \ "$(riscv32-esp-elf-gcc -march=rv32imc -mabi=ilp32 -print-libgcc-file-name)" qemu-system-riscv32 -M esp32c3 -nographic -semihosting -kernel app.elf
Вывод после старта содержит баннер загрузчика Espressif-QEMU (строки Not initializing SPI
Flash и Loading kernel at address) — это шум эмулятора, не программы; ПОСЛЕ фильтрации
этих строк вывод совпадает с остальными шестью таргетами и с интерпретатором. Именно так его сверяет
СиQemuDiffTest — седьмым таргетом в общем списке, с собственным полем-фильтром баннера.
Границы подмножества (честно)
Стадия 1 — прямолинейное подмножество: целое, логическое, строка-литерал; арифметика (плюс/
минус/умножить на, целочисленное деление нацело/остаток), сравнения
(в т.ч. строк — равно/раньше/позже через побайтовый strcmp, а не сравнение
указателей), предикаты (чётно/нечётно/положительно/отрицательно;
пусто — для строки), не/и/или; если…то…иначе и
выбор: (сопоставление/охранники → вложенный тернарник; «иначе» обязательно); связывания -- это
(в т.ч. локальные где внутри глагола — компилируются в C-локали); свои глаголы (параметры,
рекурсия — хвостовая идёт C-циклом, стек не растёт); напечатай; проверки проверь:
(равенство/сравнение — как C-assert: молчит при успехе, печатает и выходит при провале).
Точное деление
разделить на — ВНЕ МК-профиля (число под МК
фиксированное, дробей нет) — используй нацело. Число под МК — фиксированное 64-битное
(long): переполнение молча заворачивается по модулю 2⁶⁴, как в C, — числовой башни (целое
без предела/дроби/десятичное) на этой стадии НЕТ. Вне подмножества (числовая башня — дроби/десятичное/
плавающее, коллекции, модульность и т.д.) эмиттер честно бросает ошибку «под МК: …» вместо молчаливо
неверного C. Рост подмножества идёт отдельными стадиями: свои глаголы, где-связывания и
конкурентность-задачи (FreeRTOS) — уже здесь; коллекции/куча, каналы между задачами, периферия — впереди.
Конкурентность на МК: задачи FreeRTOS (стадия 2)
Стадия 2: конкурентность Глагола исполняется на настоящем RTOS. поручи X пускает
вычисление X отдельной задачей и возвращает обещание; дождись забирает
результат.
сумма -- это дождись (поручи (2 плюс 3)) произведение -- это дождись (поручи (10 умножить на 4)) напечатай сумму напечатай произведение прим 5, затем 40
Под МК обещание передаётся через уведомления задачи (task notification), а не через очередь —
без буферов, ради экономии памяти и тактов (критично на МК). Каждое обещание получает свой индекс
уведомления, поэтому дождись берёт результат нужной задачи. Компилятор порождает главную
задачу (весь верхнеуровневый код), задачу-вычислитель на каждый поручи, и точку входа с
планировщиком; не-конкурентные программы FreeRTOS не тянут.
Проверено прогоном на Cortex-M3 в QEMU (пример выше печатает 5 и 40 — как
интерпретатор). FreeRTOS вендорено в мк/freertos/ (лицензия MIT). Каналы/сообщения между
задачами и реактивное «когда/делай» на прерываниях — следующие стадии.
Глоссарий терминов
Однозначные определения понятий «Глагола» (термины легко перепутать — фиксируем):
- Число · дробь
- Один тип число с автопродвижением: целое (без предела) и дробь
p/q. Точность по умолчанию:1/3=⅓,0.1+0.2=0.3 ровно (§Числа). - Нацело (
//) - Целочисленное деление (округление к нулю):
7 // 2=3. Пара к остаток от деления (%). Обычное/— точное, даёт дробь. - Плавающее · приблизительно
- Неточное
double(IEEE) для науки/скорости — по запросуприблизительно Xили из функций (корень/синус/логарифм…). Заражает точное (правило Scheme); ∞/NaN как в IEEE (§Плавающее). - Десятичное · деньги · округли
- Точное число с запомненным масштабом (Java-
BigDecimal) для денег/ERP.деньги X=округли X до 2 знаков; примитивокругли X до N знаков [режим](банковски/вниз/вверх/точно). Показ хранит хвостовые нули (§Деньги). - Высокая точность (√/π/e до N)
корень X до N знаков,пи до N знаков,е до N знаков— материализация до любого числа верных знаков → десятичное. НЕ символьная математика (√2 как объект / CAS — не поддержано; §Высокая точность).- Алгебраическая структура · класс · алгебра
- Структура — конкретный тип с операциями
(комплексные, GF(5), кватернионы). Алгебраический класс —
кольцо/поле/тело: диктует обязательные операции и законные операторы (кольцобез ÷). «Алгебра» в узком смысле — алгебра над полем (векторы с умножением); в широком мы зовём всё это «алгебраическими структурами». Оператор+ − × ÷над значением-экземпляром диспетчится на его структуру (§Алгебраические структуры). Это НЕ CAS: законы объявляем классом, не проверяем. - Словесный стиль
- Многословная явная запись для начинающего:
плюс,равно,больше. Один из двух стилей (§11.1). - Символьный стиль
- Лаконичная запись для профессионала:
+,=,>. Второй стиль; обе формы — синонимы над одним ядром. - Прозой / построчно
- Режим терминатора предложения: построчно (по умолчанию) — конец
строки завершает фразу; прозой — завершает точка, переносы незначимы. Ось синтаксиса
предложений, не именования (директивы
далее прозой/далее построчно). - Начертание
- Как один и тот же исходник отображается: клавиатурный ASCII-канон ↔ типографский
вид (ввод
<=→ показ≤;x1→x₁). - Вид глагола
- Несовершенный (
помни) — ленивое вычисление; совершенный (запомни,сохрани) — эффект/строгое. Вид несёт «когда/как». - Падеж = роль
- Роль аргумента задаёт падеж или предлог, а не позиция:
от Ивана к Петру=к Петру от Ивана. - Ярлык-ёлочки «…» · лапки "…"
- Метка или составное имя. Ёлочки — основная форма (в т.ч. для машин/ИИ); лапки — та же роль с клавиатуры (для людей). §95.
- Запись · сущность · контроллер · приложение · служба
- Классы-POJO с предзаполненными аннотациями: без аннотаций · @Entity · @RestController · @SpringBootApplication · @Service. Родная поверхность над Java/Spring.
- Хранилище · встрой
- Репозиторий Spring Data · внедрение зависимости (@Autowired).
- Библиотека · раздел
- Модули-пространства имён: библиотека — переиспользуемая библиотека определений; раздел — нейтральное пространство имён.
- Открывает · из · подключи
- Экспорт (публичное лицо модуля) · квалифицированный доступ
(
площадь из «Модуль») · импорт модуля. - Слой
- Слой Чистой Архитектуры (
сущности → правила → адаптеры → инфраструктура) с Правилом Зависимостей: зависеть можно только внутрь, к стабильному. - Типа
- Приписывает тип имени, аргументу или результату:
число типа Число,ряд типа список чисел. Тип необязателен — без него язык выводит его сам. - Внешнее
- Тип значения, пришедшего из Java-мира: безопасная отметка «здесь наш вывод типов заканчивается» (язык не выдумывает про такое значение лишнего).
- Свободная · обычная · строгая
- Уровни строгости проверки типов: только предупреждения (программа всё равно бежит) · предупреждения на явных противоречиях · ошибка с остановкой. Уровень задаётся на модуле; ближняя пометка важнее общей настройки.
- Договор
- Пред/постусловия глагола:
требует(на входе),гарантирует/обеспечивает(на выходе, ссылается нарезультат). - Проверь · проверка
- Встроенные тесты: инлайн-утверждение · блок с таблицей случаев (§21).
- Пометка
- Комментарий:
прим(до конца строки),прим+(с пояснением). - Роль
- Функция ОДНОГО аргумента, заданная падежом/предлогом: объект (без предлога),
инструмент (
с), адресат (к), источник (из/от), предложные (в/на/по/…). Механизм №1 языка: падеж = роль → свободный порядок слов. - Сигнатура (падежная рамка)
- Рамка глагола ЦЕЛИКОМ — мультимножество ролей его параметров
(роль + кратность). Это НЕ типы! В большинстве языков сигнатура = типы+арность; у нас перегрузы
различает управление — какими предлогами/падежами глагол управляет:
добавить X в набор→ {объект, в},добавить X к списку→ {объект, к}. «Роль» — про один аргумент, «сигнатура» — про всю рамку. - Прелюдия
- Стандартная библиотека «Глагола», написанная на самом Глаголе. Подмешивается
в каждую программу автоматически — без
подключи; поэтомусодержит,объединение/пересечение/разность,ключи/значениядоступны сразу. Живёт впрелюдия.гл; операции — обычные глаголы поверх «дверей» ядра (элементы,множество из), а не встроенные. Своё определение с тем же именем затеняет библиотечное. Имя — по традиции языка Haskell (Prelude); суть — «то, что доступно всегда». - Ядро
- Неприводимый минимум языка: типы значений, литералы, равенство, доступ
[], «двери» разбора/сборки коллекций. Всё, что можно выразить на Глаголе, живёт не в ядре, а в прелюдии.
Подсветка в редакторе
Файлы .гл можно подсветить в VS Code, IntelliJ IDEA и (ожидаемо) OpenIDE через
TextMate-грамматику. Скачать и пошаговые инструкции по установке — на странице
«Инструменты».