Структуры и типы: форма предметных данных
Код в этой главе записан в прежней поверхности языка — со словами
категория,объект,утилита. Сегодняшний компилятор её слова читает, но программой такой файл не считает: файл, где есть только утилиты,flang checkотклоняет. Разбор задачи в главе верен; синтаксис переносится по таблице из главы «Старые модели».
Эта глава — про то, из чего складывается вход утилиты и свидетельство морфизма: пять встроенных типов, необязательность, именованные состояния и вложенность, а также честная граница того, что компилятор проверяет автоматически, а что нет.
Минимальный пример
категория «Счета»
структура «Строка счёта»
номер является строкой
сумма является деньгами
«срок оплаты» является датой
просрочен является признаком
комментарий иногда является строкой
Структура описывает только наблюдаемые поля. Она не является классом и не
содержит поведения — ни методов, ни конструктора, ни значения по умолчанию,
кроме иногда является, дающего полю право отсутствовать.
Что делает компилятор
Каждое поле в каноническом JSON — пара { name, type }. type — это либо
одно из пяти встроенных канонических имён (Строка, Число, Дата,
Деньги, Признак), либо имя именованного состояния, либо имя другой
структуры для вложенности. Необязательность не отдельный флаг, а суффикс
самой строки типа: Строка | undefined. Дальше именно эту строку читают
executeUtility (рантайм-проверка входа), generateTypeScript (маппинг в
TS) и любой сторонний адаптер формы — все три используют один и тот же текст
поля, не токены парсера.
Встроенные типы
| FTS | Канонический тип | JavaScript |
|---|---|---|
является строкой |
Строка |
string |
является числом |
Число |
number |
является датой |
Дата |
ISO-строка на границе |
является деньгами |
Деньги |
конечное number в текущем runtime |
является признаком |
Признак |
boolean |
иногда является … |
T | undefined |
поле может отсутствовать |
Дата на уровне рантайм-проверки — это просто строка: executeUtility
отличает её от Строка только в канонической модели, а не в момент
исполнения, поэтому "вчера" пройдёт проверку типа так же легко, как
"2026-08-03". Парсинг и валидация календарной даты — задача приложения.
Деньги — тоже обычное конечное число (Number.isFinite, без -0); для
валютной точности production-приложение должно заранее выбрать minor units
или decimal-адаптер, FTS не исправляет IEEE 754 магически.
Есть и менее очевидная граница: если после «является» стоит слово, которого
нет в этой таблице и которое не начинается с «состоянием», компилятор не
откажет. Строка валюта является Валюта компилируется без ошибок — «Валюта»
становится типом-меткой, даже если структуры с таким именем нигде не
объявлено. Проверки, что произвольный тип поля ссылается на существующую
структуру, в validate нет; это отличает такое поле от вложен объект,
где имя одновременно и тип, и (по соглашению, не по принуждению компилятора)
ссылка на другую структуру модели.
Состояние не является вложенной структурой
объект Заказ
«готов к отгрузке» является состоянием «Готов к отгрузке»
Канонический тип такого поля — не обёртка вроде Состояние, а буквально имя
самого состояния: "type": "Готов к отгрузке". Так домен морфизма
«Готовый заказ можно отгрузить» (строка если «Готов к отгрузке») совпадает
с типом поля напрямую, без промежуточного уровня. Именно поэтому подмена
состояния на структуру не работает: у структуры нет понятия «допустимый
переход», а морфизм не умеет сверяться со списком полей — они говорят на
разных языках канонической модели, хоть и живут в одном JSON.
Если у объекта действительно есть составные данные — не одно значение состояния, а несколько полей вместе — используйте вложенность:
объект Заказ
вложен объект «Адрес доставки»
Тип такого поля — это имя вложенной структуры («Адрес доставки»). Компилятор
не разворачивает поля вложенной структуры внутрь родительской автоматически:
адаптеру, который строит форму или таблицу, придётся самому найти структуру
с этим именем в document.structures, если нужны её поля, а не только имя.
Во что превращается структура
compile(source) возвращает массив structures. Адаптер может превратить
поля в UI без повторного перечисления:
const controls = document.structures
.find(({ name }) => name === "Строка счёта")
.fields.map((field) => ({
name: field.name,
required: !field.type.includes("undefined"),
control: field.type === "Признак" ? "checkbox" : "text",
}))
Это не означает, что FTS знает весь дизайн формы. Layout, подсказки, async- валидация и доступность остаются ответственностью UI-компонента. FTS устраняет дублирование имён, типов и обязательности.
Как типы попадают в TypeScript
generateTypeScript читает структуру, привязанную к утилите как принимает,
и для каждого поля вызывает ту же функцию маппинга, что и рантайм-проверка:
Строка/Дата → string, Число/Деньги → number, Признак →
boolean, суффикс | undefined сохраняется как есть. Если тип поля — имя
состояния или произвольная метка вроде «Валюта» из примера выше, генератор
не падает, но и не угадывает: он честно выведет unknown. Это ожидаемо —
утилиты в FTS принимают плоские скалярные структуры, а не объекты со
состояниями; поле-состояние в структуре, которую использует утилита, почти
всегда сигнал, что модель смешивает две разные задачи (вычисление и вывод) в
одном объекте.
Практика в песочнице
Найдите в JSON поле «готов к отгрузке» структуры Заказ. Его type
должен буквально совпасть со строкой «то» в морфизме «Готовый заказ можно отгрузить».
Откройте FtsInput0 в сгенерированном коде. Посчитайте, сколько полей стали
number, а сколько — boolean (правильный ответ: три и один). Обратите
внимание, что вес и расстояние — разные единицы измерения, но
компилятору обе они видны просто как Число.
Замените «состоянием» на «состояние» в одном месте (уберите последнюю
букву). Получите диагностику FTS_NATURAL_FIELD и верните текст обратно.
Типичные ошибки
FTS_NATURAL_FIELD — строка поля не подходит под форму «имя» является типом или «имя» иногда является типом. Пропущенное слово «является» —
частая причина: сумма деньгами вместо сумма является деньгами даёт
именно эту диагностику с номером строки. Правка — вернуть ключевое слово
является/иногда является между именем поля и типом.
FTS_UTILITY_PROPERTY — свойство утилиты нарушено на конкретном
примере. Свойство не чинит результат автоматически: если правило прибавляет
200% вместо заявленных в постусловии максимум 20%, testUtilities пометит
пример как непройденный с этим кодом в тексте ошибки, а прямой вызов
executeUtility выбросит его как диагностику. Правка — исправить либо
правило, либо (если правило верно, а предел устарел) сам текст свойства; и
то и другое требует осознанного решения, а не подгонки числа.
Чек-лист
- Я знаю пять встроенных типов и то, что
ДатаиДеньгипроверяются в рантайме мягче, чем кажется по названию. - Я объясняю разницу между полем-состоянием и полем — вложенным объектом.
- Я понимаю, что тип поля с произвольным словом после «является» не проверяется на существование соответствующей структуры.
- Я умею предсказать, во что превратится каждое поле структуры в сгенерированном TypeScript.