К README · Указатель документации
Раскладка проекта на flang
Этот документ не список пожеланий. Каждое правило здесь выведено из работающего примера — examples/library-api, REST-сервиса библиотеки, — и на этот пример ссылается.
Половина примера с тех пор снята: хозяин на Node ушёл 20 августа 2026 вместе с остальной оснасткой на JavaScript. Поэтому правила про то, что остаётся хозяину, показать на дереве больше нечем — они помечены прямо там, где стоят, и опираются на README примера, который называет снятые файлы поимённо. Правила про модули на flang проверяются на дереве по-прежнему.
Пример устроен так:
examples/library-api/
lib/ flang чистые функции над данными проекта, включая предметные правила
stdlib/ flang библиотека проекта: не знает про предметную область
Каталогов было четыре. host/ (HTTP, хранилище, percent-декодирование на Node) и test/ сняты 20 августа 2026 вместе с остальной оснасткой на JavaScript, и в дереве их больше нет — провенанс в README примера. Правила про границу с хозяином от этого не изменились: граница видна и по одному краю, а какой код оставался за ней, README называет поимённо. Там, где ниже сказано «в хозяине примера», речь о снятом хозяине, и это сказано прошедшим временем.
1. Три слоя и граница между ними
Правило. Логика уезжает в модуль flang, если у неё может быть пример. На хозяине остаётся то, у чего примера быть не может.
Почему. Пример — не документация, а исполняемая проверка: flang test находит их сам и падает, если поведение разошлось. Та же арифметика, написанная в обработчике HTTP, проверяется только тем тестом, который кто-то не поленится написать отдельно.
Где смотреть. В хозяине примера (examples/library-api/host/server.mjs, снят вместе с реализацией языка на JavaScript) не было ни одного числа из тарифа штрафов, ни проверки контрольной цифры ISBN, ни разбора строки запроса, ни условия «кому можно выдавать книги». Всё это лежит в файлах examples/library-api/lib/*.flang, проверяемых командой.
Что остаётся хозяину и почему именно ему. Ввод-вывод в языке ОПИСЫВАЕТСЯ, но не выполняется: вариант «Прочитать файл» с путь равным … строит значение — поручение, — а исполняет его хозяин (flang/SPEC.md, раздел «Ввод-вывод»). Поручений двадцать, набор закрыт, и соединения в нём есть — «Принять соединение», «Прочитать из соединения», «Ответить в соединение». Граница проходит не по ним, а по правилу этого раздела: у HTTP-сервера нет входа, на котором можно объявить пример. Он ждёт входящих, а не считает ответ по доводам, и проверять в нём примерами нечего. Поэтому сервер, маршрутизация и хранилище остаются хозяину — не потому, что язык их не выговаривает, а потому, что от переноса они не станут проверяемыми.
Чего в этом абзаце БОЛЬШЕ НЕТ, и это важно, если вы читали его раньше. Здесь стояло «функции в flang не являются значениями первого класса» и «ввода-вывода в языке нет вовсе». Оба утверждения устарели: функция теперь ЯВЛЯЕТСЯ значением (снятие функций-значений по Рейнольдсу, flang/SPEC.md, раздел 3), а поручения ввода-вывода в языке есть и исполняются командой flang io. Правило про границу пережило обе правки, потому что опирается оно на примеры, а не на список того, чего в языке нет.
Проверить, что граница проведена верно, можно печатью. flang emit печатает библиотеку проекта целиком во все восемь целевых языков — c, go, rust, python, java, csharp, elixir, js, — а хозяин не печатается никуда, потому что печатать нечего. Если после переноса очередного куска в lib/ печать сломалась, кусок взят не тот.
Исключение, которое тоже правило. Не переписывай на flang то, что платформа уже делает правильно. Percent-декодирование в примере делал decodeURIComponent снятого хозяина, а не таблица кодовых точек на flang: второй источник истины ради принципа хуже, чем один чужой.
2. Предметные правила и вычисления — разные модули
Правило. Модуль, в котором записаны тарифы, разрешения и формы данных, держится отдельно от модуля, который считает. Первый читает и правит не только программист, второй — только программист.
Почему. Их правят по разным поводам и с разной частотой. Тариф штрафа меняет библиотека, когда меняет тариф; контрольная цифра ISBN не меняется никогда. Сложи их в один файл — и правку тарифа придётся вносить в файл, который страшно трогать.
Где смотреть. lib/fine.flang — тариф штрафа и его потолок: три надбавки (50, 150 и 300 — в сумме ровно потолок), пять примеров и одно обеспечивает, читается без программиста. lib/isbn.flang — разложение строки на символы, свёртка с записью-аккумулятором, остаток от деления: тому, кто правит тариф, здесь делать нечего.
Потолок пишут обещанием, а не подрезкой. В fine.flang стоит обеспечивает «Штраф ограничен» результат не больше 500, и предел равен ровно сумме всех надбавок. Подрезать значение вместо обещания значило бы СКРЫТЬ выход за тариф; обещание же говорит обратное — выйти за 500 нельзя ни на одном входе. Причина записана в шапке файла.
Чего этим способом уже не получить. Право выдать книгу когда-то принимала не функция, а доказательство: по снимку данных строился сертификат, и отказ назывался «предпосылка не нашлась», а не «условие ложно». Сегодня это обычная функция (lib/loan.flang) — тот же ответ на тех же входах, но подкреплён он только своим телом и примерами. Разница между «доказано» и «посчитано» названа в шапке файла, а не потеряна молча.
3. Имена файлов
Правило. Имя файла — латиницей, строчными, слова через дефис. Расширение говорит, чем файл проверяется: .flang — языком, всё остальное — оснасткой того языка, на котором написан хозяин.
Почему латиницей. Имя файла едет в командные строки README, в образцы CI, в --out кодогенерации и в URL репозитория. Часть этих мест ломается на пробелах и кириллице молча — а молча ломающийся путь дороже красивого имени. Так уже устроен весь репозиторий: late-fee.flang стал бы fine.flang, credit-limit.flang, lists.flang.
Почему расширение важнее договорённости. Загрузчик flang выбирает разбор по суффиксу: .flang идёт через разборщик языка, .json — как готовый AST («Загрузить» в flang/self/link.flang). Файл с неверным расширением разберётся не тем разбором и даст непонятную ошибку.
Имя — про содержание, а не про слой. isbn.flang, а не isbn-module.flang: слой уже назван каталогом.
Когда иначе. Суффикс, который ищет инструмент, важнее правила: если прогонщик вашего хозяина ищет файлы образцом, имя подчиняется образцу, а не этому разделу. Документ проекта — README.md даже если написан по-русски: так велит правило именования документации в README.ru.md — README.md и SPEC.md рядом с кодом остаются под этими именами на том языке, на котором написаны, потому что GitHub показывает их титульной страницей каталога. Суффикс X.ru.md — для документов в docs/, где он отличает язык.
4. Имена внутри файлов
Правило. Имена функций, типов, полей и правил — по-русски. Имя из одного слова можно писать без кавычек, из нескольких — обязательно в ёлочках: «Рассчитать штраф», «дней просрочки».
Почему не латиницей. Поверхность языка русская целиком, и правило читает тот же человек, который читает его название. Смешение алфавитов в одной строке — лишняя работа глазу без единой выгоды. Английская поверхность у языка есть, но она отдельная и целиком — examples/rosetta/factorial-english.flang рядом с factorial.flang, а не вперемешку в одном файле.
Почему ёлочки. Имя из нескольких слов иначе не отличить от продолжения конструкции; кавычки — не украшение, а граница токена. Обычные кавычки равноправны, ёлочки выбраны потому, что внутри строковых литералов стоят именно " — и глазу проще, когда имя и строка выглядят по-разному.
Форма имени постоянна. Язык намеренно не угадывает падежи (docs/site/language.ru.md), поэтому имя в вызове пишется так же, как в объявлении. Послабление есть только для локальных имён внутри функции flang и только по неизменяемой основе; на имена функций, типов и полей оно не распространяется.
Как называть.
| Что | Форма | Пример из проекта |
|---|---|---|
| функция-действие | глагольная фраза | «Рассчитать штраф», «Разобрать запрос», «Отобрать» |
| функция-предикат | утверждение | «Код верен», «Заявка принята» |
| тип и запись | существительное | «Книга», «Параметр», «Сводка» |
| поле записи | строчными, как в данных | «на полке», «дней просрочки» |
Имён вроде «Помощник», «Утилиты», «Общее» в проекте нет и быть не может: связывание сливает объявления в одну программу, и совпадение имён — ошибка FLANG_DUPLICATE_NAME, а не перекрытие (flang/self/link.flang). Имя обязано быть узнаваемым во всей собранной программе, а не только в своём файле.
Когда иначе. Наружу, в HTTP, уходят латинские имена: пути /books, /loans, /returns и параметры author, shelf. Их читает клиент, который про модель ничего не знает, и они — часть протокола, а не предметной области. Ключи JSON-тела при этом остаются русскими («код», «на полке»), потому что это поля модели: второй словарь имён пришлось бы держать в согласии вручную, а рассогласование двух словарей не ловит ни один тип (так было в снятом хранилище хозяина — host/storage.mjs, файла в дереве больше нет).
5. Каталоги
Правило. Каталог — это слой, а не сущность. Не книги/ и выдачи/, а lib/, stdlib/ и — если хозяин у проекта есть — host/.
Почему. Слой отвечает на два вопроса сразу: кто это правит и чем это проверяется. lib/ и stdlib/ проверяются flang check|test; хозяин — обычный код, у которого этих проверок нет вовсе. Разложи то же самое по сущностям — и в одной папке окажутся файлы, проверяемые разными командами.
Проверка правила. Посмотри на файл и скажи, какой командой он проверяется. Не можешь ответить сразу — файл лежит не там.
6. Модули: где проходит граница
Правило. Один модуль — один файл. Первая строка — модуль «Имя», и это имя обязано совпасть с тем, как модуль импортируют.
Почему. Расхождение — ошибка FLANG_IMPORT_NAME (flang/self/link.flang): читатель видит одно имя, а получает объявления из другого файла.
Граница модуля — по вопросу «что меняется вместе». Правила отбора книг меняются вместе; формула контрольной суммы ISBN с ними не меняется никогда — поэтому lib/catalog.flang и lib/isbn.flang разные файлы.
Модуль не знает про своих потребителей. lib/query.flang не знает ни про книги, ни про ISBN: любой проект со строкой запроса взял бы его как есть. Знание про книги живёт этажом выше.
Граф импортов — дерево, слои сверху вниз. Цикл — ошибка FLANG_IMPORT_CYCLE, и это не ограничение, а подсказка: цикл означает, что граница проведена не там. В примере:
api.flang ──> catalog.flang ──> isbn.flang
│ └─> flang/stdlib/lists.flang (только «Минимум», «Все не меньше»,
│ «Сумма», «Максимум», «Длина»)
├─> query.flang ────> stdlib/text.flang (только «Первая часть», «Хвост через»,
│ «Непустые»)
├─> fine.flang
└─> loan.flang
Два имени в списке только у «Каталога» этот файл сам не зовёт — «Минимум» и «Все не меньше». Их зовёт КОНТРАКТ ввезённой «Суммы», а только сужает таблицу имён вместе с контрактами; без них модуль не собирался вовсе. Причина записана прямо в шапке catalog.flang — и это тот случай, когда список только читается не из тела файла, а из отказа связывания.
У библиотеки проекта один входной модуль. lib/api.flang — единственный файл, который загружает хозяин; всё остальное подтягивает связывание. Причина в устройстве языка: импорт — это слияние объявлений, а не пространство имён, и загрузить два модуля по отдельности значит держать на хозяине две программы и помнить, какая функция в какой.
Во входном модуле не заводят своей предметной логики. Только то, что связывает нижние модули между собой. Входной модуль, в который начали дописывать «ещё одну функцию, ей же тут удобно», через полгода перестаёт быть входным.
7. Импорт: полный и выборочный
Правило. использует «Модуль» вносит все имена модуля. только «А», «Б» вносит перечисленные.
Главное про только: он перечисляет ИМЕНА, а не замыкание зависимостей. Взяв так функцию, которая внутри зовёт соседку по модулю, получишь FLANG_UNKNOWN_NAME на первом же обращении — и, если функция объявлена тотальной, ещё и FLANG_NOT_TOTAL вдогонку. Поэтому в примере «ISBN» берётся целиком («Код верен» зовёт «Цифры» и «Контрольную сумму»), а «Списки» и «Текст проекта» — выборочно: там все нужные функции самодостаточны. Обе причины записаны прямо в шапках файлов.
Зачем вообще сужать. Не ради чистоты: конфликт имён при связывании — ошибка, а не молчаливое перекрытие. Модуль на два десятка имён, внесённый целиком, — это два десятка будущих FLANG_DUPLICATE_NAME, и первое же совпадение остановит сборку.
Пути в строке нет — модуль ищется по имени. Имя — то, что стоит первой строкой файла в модуль «Имя». Смотрят по очереди: каталог самого файла, каждый каталог выше него (пока в каталоге есть файлы .flang) и библиотека, поставленная вместе с компилятором. Отсюда следствие, ради которого всё и сделано: файл можно переложить в другой каталог, и ни одна строка использует от этого не меняется.
Вглубь поиск не идёт. Модуль из соседней ветки дерева — например, stdlib/ проекта рядом с lib/ — либо называется путём (использует «Project text» из "../stdlib/text.flang"), либо его каталог называется один раз в FLANG_MODULE_DIR (каталоги через двоеточие). Путь, когда он написан, считается от каталога файла; абсолютный путь делает проект непереносимым, и в примере его нет ни разу.
8. Библиотека проекта (stdlib/)
Правило входа. Сюда попадает функция, которая не знает про предметную область, но нужна проекту.
Правило выхода наверх. Если функция пригодилась бы всякому, кто пишет на flang, её место не здесь, а в flang/stdlib — с шапкой, примерами и разбором в общем наборе. Библиотека проекта — не свалка «пока не дошли руки».
Почему она вообще нужна. Параметрические типы в языке ЕСТЬ (тип «Возможно» от «А»), и часть flang/stdlib/lists.flang на них уже переведена: «Длина» от «А», «Приписать в начало» от «А», «Обратить» от «А» и ещё несколько работают со списком любых значений. Переведена не вся: «Элемент», «Индекс», «Без значения», «Минимум», «Сумма» объявлены для список числа и списку строк не годятся — у параметра типа нет ни порядка, ни арифметики, и граница проходит ровно по тому, сравнивает ли тело элементы. Списку строк — а из него состоит всё, что получается делением строки, — эти функции нужны заново, и писать их лучше один раз в одном месте.
Где смотреть. stdlib/text.flang — четыре функции над списком строк, ни одна не знает слова «книга».
9. Примеры
Правило. Пример — часть объявления, а не отдельный файл. пример пишется внутри самой функции.
Почему. flang test находит такие примеры сам и падает на расхождении. Пример, вынесенный в отдельный файл, никем не запускается, пока кто-нибудь не вспомнит.
Что закрывать примерами обязательно. Границу: пустой список, пустая строка, ноль, «не нашли». Такой пример есть у каждой функции проекта, у которой граница вообще бывает: «Пустой каталог», «Пустая строка», «Параметра нет», «Хвоста нет», «Вернули вовремя».
Пример на каждую ловушку, а не на каждую строку. «Разделитель внутри хвоста» в stdlib/text.flang существует потому, что склейка хвоста через = — ровно то место, где ошибаются: разрезав "поиск=a=b", вторым куском получаешь "a", а значением обязано быть "a=b".
Обещание пишут рядом с примерами, а не вместо них. обеспечивает держит все входы, пример — один; вместе они говорят разное, и одно другим не заменяется. У «Рассчитать штраф» стоит и то и другое.
Где живут функции без примеров. Их в проекте нет: у каждой функции, объявленной в lib/ и stdlib/, есть хотя бы один пример, и flang test проверяет это заодно с тотальностью. Точное число функций собранной программы здесь не названо намеренно: оно зависит от того, что втягивает связывание, и снимается прогоном, а не счётом в файлах.
10. Прогон и CI
Команды, которыми проверяется проект:
flang check examples/library-api/lib/api.flang
flang test examples/library-api/lib/api.flang
check входного модуля собирает всю программу; test прогоняет примеры всех собранных функций — своих и импортированных. Это причина, по которой достаточно назвать один файл: прибавили модуль — он приехал в проверку вместе со связыванием, без правки списка файлов.
Что здесь стояло раньше и почему снято. Стояло: «сегодня обе отвечают отказом FLANG_UNKNOWN_NAME, неизвестная функция «Все не меньше»». Функция с тех пор в библиотеке появилась (flang/stdlib/lists.flang), и catalog.flang ввозит её по имени, так что названной причины отказа больше нет. Отвечают ли обе команды кодом 0 сегодня — здесь не сказано: это снимается прогоном, а не чтением, и выдавать чтение за прогон на странице про проверяемость нельзя.
Короткий список
- Может быть пример — уезжает в модуль языка; не может — остаётся у хозяина.
.flang— предметные правила и чистые вычисления; язык хозяина — внешний мир.- Имена файлов латиницей через дефис; расширение выбирает разбор.
- Имена внутри — по-русски, из нескольких слов — в ёлочках, форма постоянна.
- Каталог — слой, а не сущность; по файлу видно, какой командой он проверяется.
- Модуль — файл; граница — «что меняется вместе»; граф импортов — дерево.
- Один входной модуль на библиотеку, своей логики в нём нет.
толькоперечисляет имена вместе с их контрактами, а не зависимости; пути относительные.- В
stdlib/проекта — то, что не знает про предметную область. - Пример — часть объявления, а не отдельный файл.
- Потолок значения пишут обещанием
обеспечивает, а не подрезкой. - Одна команда на входной модуль проверяет всю программу:
checkсобирает,testпрогоняет.