Правила, порядок и свойства: как считается решение
Код в этой главе записан в прежней поверхности языка — со словами
категория,объект,утилита. Сегодняшний компилятор её слова читает, но программой такой файл не считает: файл, где есть только утилиты,flang checkотклоняет. Разбор задачи в главе верен; синтаксис переносится по таблице из главы «Старые модели».
Утилита — чистое детерминированное вычисление над одним объектом: тариф, скидка, классификатор, лимит. Она не ходит в сеть и не пишет в базу. В этой главе — как записать несколько правил так, чтобы порядок был предсказуем, а не угадан по опыту чтения кода, и как заставить компилятор остановиться, если результат вышел за допустимые границы.
Минимальный пример
категория «Продажи»
объект Покупка
сумма является деньгами
утилита «Рассчитать скидку»
принимает Покупка
возвращает деньги
начинает с 0
правило «Большая покупка»
если сумма не меньше 10000
то добавить 10 процентов от поля сумма
пример «Ниже порога»
дано сумма равна 5000
ожидается результат равен 0
пример «Выше порога»
дано сумма равна 20000
ожидается результат равен 2000
Объект описывает форму входа. Утилита называет вход (принимает), тип
результата (возвращает) и обязательное начальное значение (начинает с).
Без него результат не появляется из неявного null или 0 по умолчанию рантайма
— ноль в примере выше написан автором модели, а не подставлен компилятором.
Что делает компилятор
- Естественный синтаксис разбирается в канонический
FtsDocument— один и тот же формат читаютvalidate, интерпретатор и генератор TypeScript. validateпроверяет структуру: у каждого правила есть условие и действие, у каждого свойства — одно сравнение результата, ссылки на поля существуют в объектепринимает.- При выполнении (
executeUtilityили сгенерированная функция) результат получаетначинает с. - Правила проходятся сверху вниз; для каждого проверяется условие.
- У правил с истинным условием выполняется действие:
то результат равензаменяет результат,то добавитьприбавляет к нему число. - После всех правил по порядку проверяются все свойства.
- Если свойство нарушено, выполнение останавливается с диагностикой
FTS_UTILITY_PROPERTY— значение наружу не уходит. - Если все свойства выполнены, вызывающая сторона получает готовый результат.
Эта последовательность одна и та же для интерпретатора и для сгенерированного
fts.utilities.ts: генератор не имеет права оптимизировать порядок правил или
пропустить проверку свойства.
Правила — это не if/else
Выполняются все правила, чьи условия истинны, в порядке объявления. Это
принципиальное отличие от цепочки if / else if, где сработавшая ветка
исключает остальные:
категория «Продажи»
объект Покупка
сумма является деньгами
«постоянный клиент» является признаком
промокод является строкой
утилита «Рассчитать скидку»
принимает Покупка
возвращает деньги
начинает с 0
правило «Большая покупка»
если сумма не меньше 10000
и сумма не больше 100000
то добавить 10 процентов от поля сумма
правило «Постоянный клиент»
если «постоянный клиент» равен да
и сумма больше 0
и сумма не больше 100000
то добавить 5 процентов от поля сумма
правило «Промокод на крупный заказ»
если сумма не меньше 15000
и сумма не больше 100000
и промокод равен «ЛЕТО2026»
то добавить 7 процентов от поля сумма
правило «Очень крупная покупка»
если сумма больше 100000
то добавить 22000
свойство «Скидка ограничена»
результат не больше 22000
пример «Обычная покупка»
дано сумма равна 5000
дано «постоянный клиент» равен нет
дано промокод равен «НЕТ»
ожидается результат равен 0
пример «Крупная покупка без промокода»
дано сумма равна 20000
дано «постоянный клиент» равен нет
дано промокод равен «ЗИМА»
ожидается результат равен 2000
пример «Крупная покупка постоянного клиента с промокодом»
дано сумма равна 20000
дано «постоянный клиент» равен да
дано промокод равен «ЛЕТО2026»
ожидается результат равен 4400
пример «Покупка ровно на потолок скидки»
дано сумма равна 100000
дано «постоянный клиент» равен да
дано промокод равен «ЛЕТО2026»
ожидается результат равен 22000
Для примера с двадцатью тысячами срабатывают три правила из четырёх: 10 % + 5 %
- 7 % от 20 000 дают 4 400. Ни одно правило не «отменяет» другое — они складываются, потому что каждое из них прибавляет к общему результату.
Потолок скидки записан числом (результат не больше 22000), а не процентом от
поля, и границы по сумме названы прямо в условиях правил. Свойство проверяется
на всей области входа, а не только там, где сработали правила: предел вида
25 процентов от поля сумма уходил бы в минус на отрицательной сумме и ронял
расчёт там, где ни одно правило не сработало. Подробный разбор —
в главе об антипаттернах.
Несколько условий одного правила соединяются словом и: каждое следующее
условие — отдельная строка того же отступа, что и если. Третье правило
сработает, только если обе строки истинны одновременно.
Порядок при этом не бессмысленный: если правило использует
то результат равен, оно перезаписывает всё, что накопили предыдущие правила.
Такое правило стоит объявлять последним осознанно, а не полагаться на то, что
компилятор сам расставит приоритеты — он этого не делает.
Сравнения и проценты
Доступны шесть сравнений: равен, не равен, больше, меньше, не больше,
не меньше. равен и не равен работают для любого скалярного значения —
числа, признака, строки. Остальные четыре требуют, чтобы обе стороны сравнения
были числами: строку с датой они не сравнят, компилятор такую утилиту не
пропустит через validate, а на рантайме сравнение несовместимых типов
останавливает выполнение.
N процентов от поля X вычисляется от значения поля X во входных данных, а
не от текущего результата. Имя поля указывается ровно так, как оно объявлено в
объекте, без падежей: 10 процентов от поля сумма, 2 процента от поля «страховая сумма». Компилятор не пытается склонять русские существительные —
это осознанное ограничение, а не недоработка.
Свойство — постусловие, а не автокоррекция
свойство «Скидка ограничена»
результат не больше 15000
Свойство проверяется после того, как отработали все правила. Если сравнение
ложно, FTS не подрезает скидку до допустимого предела и не выбирает
«безопасное» число вместо неверного — он останавливает выполнение с
диагностикой FTS_UTILITY_PROPERTY и именем нарушенного свойства. Это
осознанная граница: тихая коррекция результата спрятала бы ошибку в модели или
во входных данных, а откат наружу заставляет её увидеть и исправить в самом
правиле.
Свойство пишется как одно сравнение результата — результат <сравнение> <выражение>. Выражением может быть число, значение поля или процент от поля,
как в примере выше.
Детерминированность и границы утилиты
Одни и те же входные данные всегда дают один и тот же результат: утилита не читает часы, не звонит наружу и не хранит состояние между вызовами. Это позволяет тестировать её без моков и переиспользовать один и тот же расчёт в интерпретаторе, в сгенерированном TypeScript и в браузерной песочнице — они обязаны совпасть, потому что читают одну и ту же модель.
Хорошие кандидаты в утилиту: скидка, тариф, комиссия, классификатор, лимит, нормализатор значения, command decision («разрешить» / «отклонить» по понятным числовым порогам). Плохие кандидаты: «отправить письмо», «получить курс валют из сети», «записать заказ в транзакции». Как только вычисление зависит от внешнего мира или производит эффект, оно перестаёт быть воспроизводимым — и должно остаться в обычном коде, который вызывает утилиту, а не наоборот.
Практика в песочнице
- Откройте вкладку «Выполнить» и посчитайте скидку для суммы 12 000 без статуса постоянного клиента. Затем добавьте статус — убедитесь, что результат вырос ровно на 5 % от той же суммы, а не пересчитался с нуля.
- Откройте
delivery-price.fts(вкладкаcheck) и найдите два правила, которые вносят базовый тариф: нижнюю границу держат они, а не строканачинает с— та задаёт ноль. Определите, какое ключевое слово в свойстве отвечает за «не меньше», а не за «не больше». - В
order-discount.ftsзамените потолок15000на более жёсткий — например,результат не больше 10 процентов от поля сумма— и запуститеrunдля постоянного клиента с суммой 20 000. Скидка 3000 против предела 2000: убедитесь, что вкладка показывает диагностику, а не подправленное число.
Типичные ошибки
FTS_EXPECTED_KEYWORD— файл не начинается скатегория «Имя». Естественный разбор требует объявление категории первой строкой; без него компилятор откатывается к устаревшему скобочному синтаксису и жалуется на отсутствие буквальногоcategory. Проверьте первую непустую строку файла.FTS_NATURAL_DECLARATION— на верхнем уровне категории встречено слово, которое не являетсяобъект,структура,морфизм,теоремаилиутилита(например, опечатка вродеутилиттаили собственное словоштука). Компилятор называет допустимый список прямо в сообщении.FTS_UTILITY_PROPERTY— правила отработали, но результат не прошёл сравнение в свойстве. Это не ошибка синтаксиса, а сигнал о том, что входные данные или сами правила выводят результат за декларированную границу. Сообщение называет и свойство, и утилиту:нарушено свойство «Имя» утилиты «Имя».
Чек-лист
- У утилиты есть
принимает,возвращаетиначинает с— без начального значения утилита не компилируется. - Каждое правило начинается с
еслии заканчивается одним действием:то добавитьилито результат равен. - Несколько условий одного правила — отдельные строки
и, а не одна строка через запятую. - Порядковые сравнения (
больше,меньше,не больше,не меньше) — только для чисел; для остального естьравенине равен. - Свойство — одно сравнение результата, которое проверяется после всех правил и не пытается ничего исправить сам.
- Хотя бы один пример у утилиты обязателен для
fts test; без примеров команда завершится сFTS_NO_UTILITY_EXAMPLES— подробнее в следующей главе.