Слой доказательства: высказать утверждение и проверить доказательство
Чего не было
Про улики этого файла. Ниже проверки часто названы прогонами набора на JavaScript. Ни одного из них в дереве нет: реализация на JavaScript снята 20 августа 2026 (
fe8e8a37), и вместе с ней ушли все*.test.mjs. Здесь они названы тем, чем были, — «проверка ядра», «сверка разборщиков», — а не путём, которого не существует. Числа при них не переснимались.
До этого слоя утверждение о поведении в языке высказать было нечем.
Постусловие в AST было и работало: его проверял интерпретатор (кадр post в flang/self/interpret.flang) и печатали все восемь целей. Но кладёт его туда мост наследия FTS на JavaScript — из свойство утилиты наследия FTS, — и flang/self/defunc.flang — из сторожа объявленной меры. Поверхности у постусловия не было вовсе: обычная функция flang сказать о себе не могла ничего.
Слово теорема при этом стояло в языке с самого начала и разбиралось. Читал его только человек: узел уезжал в legacy, и оттуда его не брал никто — ни типизатор, ни ведомость, ни печать. То есть теорему высказать было можно, а следствий из неё не было никаких.
Кванторов не было. требует не было. обеспечивает не было.
Законы моноида, монады, изоморфизма и отношений множеств проверялись — и проверяются — конечной сеткой примеров автора: предел 12 значений, у монады 6, у стрелок 4. Это не доказательство и никогда им не было; ведомость доказательства (flang check --proof) для этого и появилась.
Что теперь есть
Три слоя, и границы между ними жёсткие. Индукция не завела четвёртого: принцип порождается (flang/self/proof-initial.flang) и сводится (flang/self/proof-kernel.flang) ВНУТРИ третьего слоя, а поверхность и обязательства о ней не знают ничего. Новых слов у поверхности от индукции ноль — она работает восемью уже стоящими, и это не экономия: девятое слово пришлось бы завести в таблице ключевых слов самоприменённого лексера, то есть увеличить ровно тот долг, в который эта работа и так упёрлась.
1. Поверхность: девять слов
| слово | что делает |
|---|---|
требует «имя» <утверждение> | предусловие функции; снимает его ВЫЗЫВАЮЩИЙ |
обеспечивает «имя» <утверждение> | постусловие обычной функции |
для всех <параметр> обеспечивает … | то же, с названным квантором |
утверждаем <утверждение> | цель теоремы |
индукция по <параметр> [убывает <мера>] | индукция и её случаи |
по свойству «имя» | обоснование шага: постусловие |
по примеру «имя» | обоснование шага: пример функции |
по предположению | обоснование шага: предположение индукции |
следовательно доказано | доказательство закрыто |
Плюс уже стоявшие в языке слова, взятые как есть и означающие то же самое: теорема, дано, случай, то, по закону, убывает, затем. Новых слов девять, а не шестнадцать, и это не экономия: затем в языке уже значит «следующее звено цепочки» (композиция морфизмов в порядке чтения), и второе слово о том же заставило бы читателя помнить два словаря.
Поверхность — Isar из Isabelle, и это сказано вслух намеренно. Isar придуман ровно ради читаемости структурированного доказательства, и переизобретать то, что уже себя доказало, не за чем: утверждаем — это show, дано — assume, затем — also/moreover, по свойству «…» — by (rule …), следовательно доказано — qed.
Тактик нет и не будет. Тактика — метапрограмма, ворочающая состояние цели поиском, а поиск нетотален по природе. В flang всё либо тотально, либо объявлено обычным, значит тактику на самом языке не написать — она жила бы только в свидетеле на JavaScript. Такая дыра у нас была одна (убывает не имел продукции в самоприменённом парсере); её закрыли, и она осталась примером того, чего не заводят второй раз. Сегодня продукция убывает стоит в flang/self/parser.flang и стережётся отдельной строкой (сверка разборщиков, строка 326), а весь список пробелов снимается с таблицы лексера прогоном и содержит одну запись — в монаде.
Цена решения нулевая: что бы тактика ни выдала, ядро всё равно проверяет ТЕРМ, поэтому тактики не входят в контур доверия и добавляются когда угодно позже.
Две формы под одним словом
Слово теорема несёт старую форму (наследие FTS) и новую. Различает их не слово, а содержимое блока, и решение принимается ПОСЛЕ разбора блока:
| строка | форма |
|---|---|
дано «Объект» имеет «поле» равное значение | старая: за именем стоит имеет |
дано <имя>: <тип> | новая: введённая переменная (квантор) |
дано <утверждение> | новая: гипотеза |
в данных …, по морфизму … | только старая |
следовательно «вывод» | старая |
следовательно доказано | новая — ОДИН токен qed |
утверждаем, индукция по, по свойству, … | только новая |
следовательно доказано не отнимает у старой формы её следовательно «вывод» потому, что склейка ключевых слов жадная и длиннейшая-первой — тот же довод, по которому закон и по закону мирно стоят рядом.
Смешение форм — отказ, а не склейка: у конструкции стало бы два смысла и ни одного объявленного.
Все теоремы наследия в дереве разбираются в тот же узел ftsLegacy, что и до этой работы. Это проверено на самих файлах репозитория (проверка поверхности доказательства), а не на выдуманном примере.
Слова поверхности: занятость измерена, а не обещана
У всех девяти слов ноль голых вхождений в 153 файлах .flang и .fts корпуса. Измерено не глазами и не grep-ом, а flang/scripts/word-occupancy.mjs: он читает корпус тем же tokenize, каким его читает язык, и считает только токены name с quoted: false. Закавыченное имя ключевым словом стать не может никогда, поэтому «ёлочки», строки и комментарии не считаются — этим счёт и отличается от grep, который их считает и потому всегда завышен. Скрипт можно запустить и повторить число.
Четыре слова из восьми — фразы из двух слов, и не ради красоты речи: одинокое свойство уже занято (property), одинокое пример — тоже (example), а по — это by. Фразой из двух слов переменную не назвать никогда; тем же доводом в языке уже стоят порог отказов, код символа и обратный элемент.
Слова стоят на двух поверхностях из четырёх — русской и английской. На эсперанто и по-китайски таблица молчит, и это решение владельца языка, а не пропуск: по свойству, по примеру, по предположению и следовательно доказано — обороты этого языка, а не термины математики, и переводить придуманное значило бы выдумывать дважды. Тот же довод держит на двух поверхностях decreases («убывает»). Молчание измерено (проверка поверхности доказательства), чтобы столбец нельзя было дописать за владельца молча. Пока его нет, доказательство пишется на двух поверхностях.
Десятое ЗНАЧЕНИЕ без десятого слова: промежуточный факт
Слов по-прежнему девять. Десятого не заводится — заводится смысл у записи, которую разбор строил всегда, а ядро отвергало.
случай голова и хвост
затем (длина («Вставить по порядку» от голова и («Сортировать» от хвост)))
равен ((длина («Сортировать» от хвост)) плюс 1)
по свойству «вставка удлиняет список ровно на один»
то по предположению
Что тут было раньше. Шаг вправе выписать утверждение (claim узла шага), и оно обязано было СОВПАСТЬ с целью, которую ядро построило само:
выписанное утверждение шага — не заключение этого места. Заключение ядро СТРОИТ по объявлению и телу функции, и подменить его написанным нельзя
То есть затем был вторым способом обосновать ту же цель, а не звеном вывода. Соединить две леммы, подставить равное, провести цепочку — нечем.
Что теперь. Выписанное утверждение, НЕ совпавшее с заключением, — это промежуточный факт: ядро обязано его доказать теми же правилами и из того же известного, а доказав — положить в известное для следующих шагов.
Цена — ноль слов и ноль строк печати. Узел шага уже несёт поле claim, типы его уже проверяют, до генераторов кода он не доезжает: доказательство даёт 0 байт разницы у восьми целей из восьми (замер: flang emit одной программы с теоремой и без, суммарные байты равны у c, go, rust, python, java, csharp, elixir, js).
Правил вывода не прибавилось ни одного. Прибавилось право подать существующим шести правилам другую цель. Аксиом по-прежнему ноль: всякий узел, попадающий в известное, ВЫВЕДЕН шагом выше по тексту.
Три условия, и снятие любого — дыра.
- Закрытие обязательно. Цепочка принимается, только если хотя бы один шаг закрыл построенную ядром цель («Итог по закрытию» в
flang/self/proofterm.flang). Цепочка из одних промежуточных фактов доказывает верное и НЕ доказывает заявленного. - Выписанное обязано быть ВЫВЕДЕНО, а не принято на слово: из инстанций названной леммы, допущений индукции, предусловий и уже добытых фактов.
- Круг запрещён, как и прежде:
по свойствуна доказываемое сейчас постусловие отвергается.
Каким обоснованиям промежуточный факт доступен, и это граница, а не недосмотр. по свойству «…» — факт выводится из инстанций названного постусловия на вызовах ВНУТРИ выписанного утверждения; по предположению — из допущений индукции, предусловий и уже добытых фактов. У по примеру и по закону выписанное утверждение по-прежнему не читается вовсе — так было и до этой работы, и расширять их без подделки на каждое нельзя.
Все три стоят подделками в flang/test/fixtures/poddelka-promezhutochnyy-fakt.flang и прогонялись отдельным прогоном подделок на JavaScript.
Где именно факты складываются. Ход цепочки уже копил список известно — но тратил его только на строку «известно: …» в тексте отказа, а в сведение не подавал. Поэтому усвоенные факты едут ОТДЕЛЬНЫМ списком (Обстановка терма, поле факты), узлами-допущениями формы {claim: …}: то, что копилось раньше, — пересказы для отказа, и подать их сведению было бы ошибкой.
Девятое слово: требует — предусловие
Здесь стояла запись «предусловия нет», и довод у неё был такой: постусловие ложится на уже работающую машинерию, а предусловие — это новый рантайм в интерпретаторе и во всех восьми целях печати. Довод верен ровно для одного прочтения — того, при котором предусловие проверяет вызываемый. При нём появляется проверка на входе каждой функции, умножается на восемь целей, и доказательство взамен не получает ничего: проверка в рантайме говорит о том входе, который пришёл, а утверждение нужно обо всех.
Прочтение выбрано другое, и оно то же, что в Dafny: предусловие снимает вызывающий.
| место | что происходит |
|---|---|
| вызов внутри программы | обязательство на вызывающем; не снял — FLANG_PRECONDITION_CALL |
| тело функции | предусловие — известный факт: допущение обязательства (assumptions) для ядра И уточнение среды для типизатора (flang/self/types.flang) |
граница программы (--args, примеры) | вычисляется: доказывать нечего, вызывающего нет |
| печать в восемь целей | ноль строк; измерено побайтовым сравнением |
Предусловие говорит и о каждом элементе списка (ADR-0037 §4.2, задача 3123, 13 сентября 2026). Утверждение за именем требования читается той же дорогой, что цель постусловия («Разобрать цель», flang/self/parser.flang), и потому несёт квантор по элементам ADR-0026 §11 п.14 — требует «имя» для всех э из Л: П, по-английски requires «name» for all e from L: P — и вложенные кванторы п.15; новых слов нет. До этого разбор отвечал FLANG_PARSE … «для всех» связывает элементы списка только для цели целиком (замер задачи 3123). Три следствия:
- типизатор («Проверить предусловия»,
flang/self/types.flang) типизирует выражениетребуетв среде параметров, как цель постусловия:Л— список, имя элемента — типом элемента,П— признак. До задачи выражениетребуетне типизировалось вовсе, а только сужало среду тела; сужение («Под предусловиями») узел-квантор пропускает, не сужая, — квантору сужать нечего; - ядро нового случая не получает: узел
forallInприезжает в факты как есть, и цельдля всех п из Л: П, совпадающая с предусловием, закрывается правилом «цель есть допущение» — Т1 знак в знак, Т2 с другим именем элемента. Запись печатает под целью блоквыводс шагомТ1 … строка NлибоТ2 … строка Nи развёрткой телаРазв2; сверщик на C переигрываетТ2тем же читателемвсе_элементы_вывода, что и семейство Э: список тот же знак в знак, свойство совпадает после подстановки имени, захват — тот же сторож, что у Э3 (+14 строк кода в бюджете ADR-0037 §4.5); - место вызова снимается по построению подставленного списка семейством Э без нового кода:
«Ф» от (приписать н к пустой список)при собственномтребует н больше 0вызывающей — Э3 (посылка о «н» — Т1 из фактов вызывающей) и Э1. А«Ф» от (приписать 1 к пустой список)— то, что ADR-0037 §4.2 обещал «Э3 и счётом уже сегодня», — по замеру 13 сентября 2026 НЕ снимается: «Место снято» (flang/self/proofterm.flang) зовёт сведение без программы, «Вычислить замкнутую» отвечает «Счёт не звался», а правил строгого порядка без фактов1 больше 0не берут. Это дорога задачи 1261.
Пробы — семья все-элементы (по-требованию-квантор, вызов-с-приписанным и их порчи). Печать квантора в цели печати — задача 7098, не здесь.
Семейство Э: все элементы по построению
Цель для всех э из Л: П (ADR-0026 §11 п.14) ядро выводит ТОЛЬКО по построению списка Л, и список случаев закрыт — каждое правило стоит строкой ведомости flang/proof/tables/inference-rules.tsv и леммой в flang/proof/lean/Rules.lean (модель — всеЭ, отбор, отобр в Model.lean; ловушки 11, 24, 25):
| правило | список Л | посылки | заперто |
|---|---|---|---|
| Э1 | пустой список (пустой список, в записи и []) | — | — |
| Э2 | отфильтровать Л' где х → У при У ≡ П до имени связанной | — | чужое У (ловушка 11) |
| Э3 | добавить Х к Л', приписать Х к Л' | П[э:=Х]; ∀э∈Л'. П | П связывает э или имя, свободное в Х |
| Э4 | если У то А иначе Б | ∀э∈А. П; ∀э∈Б. П | — |
| Э5 | выписанный [Х₁, …, Хн] (ADR-0037 §4.3) | П[э:=Х₁]; ∀э∈[Х₂…Хн]. П | как у Э3 (ловушка 24 — без посылки о голове) |
| Э6 | отобразить Л' как х → Ф (ADR-0037 §4.3) | ∀х∈Л'. П[э:=Ф] | П связывает э, х или имя, свободное в Ф; х свободно в П (ловушка 25 — тот же предикат без композиции) |
Гипотеза индукции и предусловие закрывают цель правилом допущения (Т1, с другим именем элемента — Т2), а не новым случаем; список из вызова чужой функции по построению не выводится — о нём говорит её постусловие (восьмой ход).
Посылка Э3/Э5 о члене доказывается обычной целью при тех же фактах либо счётом замкнутой формулы («Выч» в записи). Посылка Э6 — та же цель с другими полями (прообраз, имя элемента отображения, тело П[э:=Ф]), и её снова сводит это же правило: у отобразить функции как ф → ф от 1 при требует для всех ф из функции: (ф от 1) не меньше 0 посылка совпадает с предусловием знак в знак — это и есть «обещание второго порядка» ADR-0037 §3, конечная конъюнкция над ярлыками функций.
Запись печатает блок вывод шагами Э (flang/self/proof-record.flang, «Шаги всех элементов записи»; хвост выписанного списка пишется [] — по мере длины формулы), сверщик на C переигрывает их приёмами семейства Э в шаг_вывода; цена сверщика по ADR-0037 §4.5 — 52 строки кода из 60 (3123 — 14, 4058 — 38). Применение выписанного ярлыка (функция «Ф») от а (Яр1, ADR-0037 §4.3) — переписка, не правило ядра: строка «Яр1» ведомости в семействе «Разв», лемма «Яр1» и ловушка 26. Ядро читает его в одном месте — в СБОРЕ фактов места вызова (flang/self/proofterm.flang, «Ярлыки в узле», «Факты ярлыка»): в теле верхнего квантора над выписанным списком с каждым членом на месте имени элемента ищутся применения ярлыков, и доказанное обещание «Ф» без требует берётся о самом узле применения (результат ↦ узел, параметры ↦ его доводы). Такой факт совпадает с целью члена после Э5 деревом и закрывает её Т1: место «Применить все к единице» от [функция «Удвоить», функция «Квадрат»] при требует для всех ф из функции: (ф от 1) не меньше 0 снято. Ярлык без обещания, с недоказанным обещанием или с требует факта не даёт — место остаётся отказом. Восьмой ход тел ярлыка не читает. Место вызова стоит в записи блоком снятия (ADR-0038), но вывода «все элементы по построению» в нём печать не даёт, и сверщик держит место на слове ядра: шагу Яр1 в записи взяться неоткуда, и сверщику он не стоил ни строки. Получит блок вывод — Яр1 потребует приёма. В блоке тотальности записи ярлык функции в теле называется зовомым наравне с вызовом (flang/self/obligations.flang, «Зовы тела»): функцию, переданную значением, зовёт тот, кто её принял, и сверщик так её и читает. Пробы — семья все-элементы.
Вторая половина строки «тело функции» приехала позже первой, и до неё слово стоило дороже, чем приносило. Ядро читало предусловие допущением, а типизатор не читал вовсе: требует «непуст» не пусто эл обязывало каждого вызывающего и НЕ ДАВАЛО ТЕЛУ НИЧЕГО — голова эл внутри такой функции оставалась частичной формой, и печать ставила на неё проверку во время работы. Теперь предусловие уточняет среду тела ТОЙ ЖЕ функцией сузить, которая читает условие если: второго чтения условий не заводится, и потому предусловие даёт ровно то, что даёт если той же формы — ни больше, ни меньше. Мерено местЧастичныхФорм: у функции с телом голова эл без предусловия 1 место, с предусловием 0, а вход эл = [] отвергается на границе кодом FLANG_PRECONDITION с именем требования — вместо FLANG_BUILTIN_ARGS изнутри, то есть отказ стал называть НАПИСАННОЕ АВТОРОМ вместо случившегося внутри.
Уточняется только тело. Ни мера, ни постусловие, ни сами предусловия среды не меняют: тело — единственное место, где стоят частичные формы, ради которых уточнение заведено, а предусловие, уточняющее само себя, читало бы допущением то, что проверяет.
Досягаемость этого хода измерена, а не оценена. Уточнение несёт ОДНУ величину — нижнюю границу длины, — и потому берёт ровно те частичные формы, чьё условие есть «длина не ноль»: голова, хвост, код символа, разделить. На корпусе это 60 мест из 279 (голова 10, хвост 21, код символа 1, разделить 28). Формы про НОМЕР — элемент 137, подстрока 73, символ 2, итого 212 из 279 (76 %) — этим ходом НЕ берутся, и проверено это прямо: требует «в границах» н меньше (длина эл) место не снимает. Причина названа, и она не в лени: сужение читает сравнение имени с ЛИТЕРАЛОМ, а длина эл литералом не является; и даже прочитай оно его, класть было бы некуда — длинаНиз есть ЧИСЛО у одного типа, а здесь нужна связь ДВУХ имён («н не больше длины эл»). Это другой ход и отдельная работа. Оставшиеся 7 мест — к числу, и они ни про номер, ни про пустоту.
Сколько из этих 60 корпус ВЫДЕРЖАЛ, и на чём он встал
Написать требует мало: предусловие у библиотечной функции — это долг, и платят его все вызывающие. Поэтому досягаемость хода была померена не рассуждением, а попыткой: пятнадцать предусловий написаны разом по всему классу разделить/голова/хвост/код символа, и корпус проверен целиком.
Итог первой попытки: 76 отказов FLANG_PRECONDITION_CALL на 21 месте вызова. Свод при этом сдвинулся с 279 мест у 195 функций до 277 у 193 — на два места, ровно на те две функции, у которых красных мест не оказалось («Разбить по символу» и «Считать символ» в stdlib/strings.flang).
Но и эти два не устояли, и вторая улика важнее первой. Дифференциальный прогон проверки печати в JavaScript («stdlib и leetcode: напечатанный JS совпадает с интерпретатором») покраснел на входе ["а,б,в", null]:
интерпретатор дал
FLANG_BUILTIN_ARGS: «пусто»: ожидается строка или список, получено ничто, напечатанный JS далFLANG_BUILTIN_ARGS: «разделить»: разделитель должна быть строкой, получено ничто
Оба отказали, но РАЗНЫМ отказом, и сверка права. Причина — не корпус и не сила ядра, а столкновение двух решений, записанных в этой самой главе: (1) предусловие проверяется ВЫЧИСЛЕНИЕМ на границе программы; (2) в печать оно не уезжает НИ ОДНОЙ строкой, и это измерено побайтово. Вместе они значат, что на входе ВНЕ ДОГОВОРА интерпретатор отказывает раньше печати. Раньше обе реализации отказывали в одном месте — внутри разделить, — и совпадали.
Ослабить тут нечего ни с одной стороны: сетка входов у сверки враждебна НАРОЧНО (42, ничто, список вместо строки — «показаны зубы»), а печать предусловия не несёт по решению, у которого измерена цена. Поэтому оба предусловия сняты, свод вернулся к 279 у 195, а довод оставлен прямо в stdlib/strings.flang над обеими функциями — чтобы улику не пришлось открывать заново.
Отсюда заказ, и он предшествует любому требует в печатаемом корпусе: сверка обязана знать про ДОГОВОР — на входе, нарушающем объявленный тип или предусловие, от двух реализаций законно требовать «обе отказали», а не «отказали одним текстом». Пока этого нет, требует применимо только там, куда дифференциальная сверка печати не достаёт.
Двадцать одно красное место разошлось на три причины, и все три — про ЯДРО, а не про то, что автор написал условие неудачно:
| причина | мест | пример |
|---|---|---|
аргумент — имя, СВЯЗАННОЕ свёртка/отобразить | 14 | «Применить замены» (все восемь целей печати): свёртка «замены» … «Заменить всё» от «акк» и («замена».«что») |
| условие ветвления ВЫЗЫВАЮЩЕГО | 3 | «Без знака»: если (длина знак) равен 0 то текст иначе «Заменить» … — сторож стоит прямо здесь и верен |
параметр вызывающего без своего требует | 4 | «Заглавная буква»: зовёт «Позиция подстроки» от своего параметра |
Третья причина лечится распространением требует вверх — и упирается в первую: «В верхний регистр» зовёт «Заглавная буква» из свёртки по списку из 26 литералов. Вторая названа в этой же главе как отсутствующая нарочно (путевых условий язык не собирает). А первая — не отсутствующее правило, а отсутствующая ФОРМА ВЫСКАЗЫВАНИЯ. Чтобы «Применить замены» могло заплатить, ему надо объявить «у КАЖДОГО элемента списка замены поле что непусто», а квантора в требует нет (см. «Чего у предусловия нет»). Литерал списка тут не спасает: замены — параметр, и утверждение про его элементы иначе как квантором не записать.
Прямой ответ на вопрос «выдерживает ли корпус честные предусловия»: сегодня НЕТ, и препятствий ДВА, оба названы выше и оба не про корпус.
- Вызывающим нечем платить — 21 место, из них 14 упираются в то, что утверждение «непусто у КАЖДОГО элемента списка» сегодня НЕВЫСКАЗЫВАЕМО: квантора в
требуетнет. Это цена входа в класс. - Печать и интерпретатор расходятся вне договора — и это препятствие срабатывает даже там, где вызывающие заплатили полностью, то есть отсекает и те две функции, что прошли первое.
Заказы отсюда следуют два и оба названы числом: договор в дифференциальной сверке (снимает препятствие 2 целиком, без него требует в печатаемом корпусе невозможно вовсе) и квантор в требует (снял бы 14 мест вызова из 21). Порядок между ними жёсткий: второе без первого не даст ни одного места.
Класс НОМЕРА: верх, выраженный именем — что он взял и почему не больше
Класс элемент/подстрока/символ — 212 мест из 279, и предыдущим ходом он не брался по причине, проверенной прямо: сужение читает сравнение имени с ЛИТЕРАЛОМ, а длина эл литералом не является; и даже прочитай оно — класть некуда, потому что длинаНиз есть ЧИСЛО у одного типа, а нужна СВЯЗЬ ДВУХ ИМЁН.
Связь двух имён заведена (flang/self/types.flang): у числового типа появилось поле — имена тех списков и строк, чьей длины это число заведомо МЕНЬШЕ. Ставит его ровно одно место — сузить, то есть если и требует; читают элемент и символ (вГраницах).
Условие одно на ОБЕ базы номера, и это необходимость. Рантайм считает at = номер − база; база равна 1 по умолчанию и 0 по ключу --index-base (командная строка на JavaScript), и ключ этот доезжает до ПЕЧАТИ, а не только до интерпретатора. Проверка типов базы не видит вовсе. Поэтому доказывается 1 ≤ н < длина эл — то, что верно при обеих. Обе половины обязательны и обе про разное: низ даёт отрезок (число против числа), верх даёт имя.
Граница теряется на арифметике — и не правилом, которое можно забыть написать. Всякое вычисление строит числовой тип заново (отрезок), а отрезок кладёт ровно четыре поля и про подДлиной не знает: «пронести границу через сложение» нечем даже выразить. Теряется она и на ПЕРЕВЯЗКЕ ИМЕНИ — знание записано именем, поэтому все шестнадцать связываний файла идут через связать, отменяющую знание о прежнем значении в той же строке.
Свод не сдвинулся: 279 у 195 осталось 279 у 195. Причины названы счётом по корпусу (214 мест класса в собственных функциях файлов):
| причина | мест |
|---|---|
подстрока: правила нет вовсе — ей нужны ЧЕТЫРЕ факта, включая А ≤ Б между двумя именами | 76 |
номер сравнивается только с ЛИТЕРАЛОМ (н меньше 10), а не с длиной | 55 |
номер — ВЫРАЖЕНИЕ (элемент (акк.номер) в …), а сужение читает имя | 36 |
в теле есть только НЕСТРОГОЕ сравнение с длиной (н не больше (длина эл)) | 14 |
| сравнения номера с длиной в теле НЕТ ВОВСЕ | 13 |
| контейнер — не имя, а выражение | 12 |
| номер — литерал, длина снизу неизвестна | 6 |
| доказано анализом | 2 |
Оба доказанных места доказаны ЧИСЛОМ, а не именем. Это элемент 1 в ряд (017-letter-combinations) и элемент 1 в пара (207-course-schedule): у контейнера известна нижняя граница длины (два), у номера — точный верх (один), и 1 < 2 считается двумя числами. Именной верх подДлиной — тот, ради которого поле заведено, — не снял в корпусе НИ ОДНОГО места: строгого сравнения номера с длиной там не написано. Сказано это здесь затем, чтобы никто не считал именную дорогу проверенной корпусом: она проверена только тестами (проверка границ обращения по номеру).
Эталон на flang — открытый долг, и его объём назван. self/types.flang несёт «Сузить имена», «Сузить по длине», «Сузить по сравнению» и отрезок отдельным вариантом «Вид отрезка» содержит «низ»: число, «верх»: число, но подДлиной там наполовину есть: два шага из четырёх сделаны.
Сделано. Поле у варианта «Вид отрезка» («подДлиной» и «неДлиннее», списки имён) и сбор границы условием — «Сузить по границе номера», «Наложить границу номера», «С границей номера», — включённый в ту же цепочку «Сузить имена», что читает если и требует.
Третий замер объёма поправил второй, и снова в сторону меньшего. Было сказано «трогает КАЖДОЕ место построения отрезка»; мест построения оказалось одно — «Тип по концам». Остальные одиннадцать вхождений — разборы (случай вариант «Вид отрезка» с «низ» как …), и они правки не потребовали: проверено прогоном, а не предположено. Единственность построения дала эталону то же свойство, что свидетелю: граница теряется на арифметике не запретом, а тем, что всякое вычисление идёт через одно место, кладущее пустые списки.
Отмена знания при перевязке имени сделана («Связать имя заново»: «Отменить границы об имени» проходит цепочку среды и вычёркивает имя из обоих списков, затем связывает). На неё переведены 14 мест связывания; три места СУЖЕНИЯ оставлены на прежней «Связать имя», и это не недосмотр: сужение («Наложить длину на тип», «Наложить границу на тип», сужение отрезка) говорит про ТО ЖЕ значение — отменять там нечего, а отмени, и сужение стирало бы само себя. Ровно та же пара есть у свидетеля: Map.set внутри сузить и связать на местах связывания.
Читатель и база сделаны, и на этом эталон закрыт целиком — четыре шага из четырёх. База едет полем «Таблицы» («база номера»), потому что таблицы и так доходят до каждой встроенной формы, а второй провод к одному числу завёл бы место, где два числа разойдутся молча; читает её «База номера программы» с той же программы и с тем же умолчанием — поля нет, значит единица. Читатель — «В границах» (плюс «Границы отрезка номера», «Контейнер годен», «Имя в границах»), включён в «Форма элемент» и «Форма символ» через ту же «Отметить место», какой отмечаются остальные частичные формы.
Порядок внутри работы выбран так, что БЕЗОПАСНА КАЖДАЯ ПРОМЕЖУТОЧНАЯ ТОЧКА, а не только конец. Отмена шла первой и одна: знание кладут, вычёркивают и никто не читает — инертное и потому неспособное соврать. Читатель без отмены — тот единственный порядок, который даёт не недоделку, а ЛОЖНОЕ доказательство.
СВЕРКА ЗАГОВОРИЛА В ПЕРВЫЙ ЖЕ ПРОГОН — и сказала не то, чего я ждал. Ждал я расхождения в переносе границы через сужение отрезка и закрыл его заранее. Сверка же назвала другое: flang/stdlib/strlists.flang, элемент|175|11 — эталон пометил место доказанным, свидетель промолчал. Причина: заслон печати стоял только на стороне свидетеля. Эталон получил тот же («Снимается непустотой», тот же перечень четырёх форм), и с ним отметки снова совпали место в место.
Это ровно то, ради чего эталон и пишется: односторонняя правка перестала молчать в тот же час, когда читатель дал сверке голос. До читателя расхождение существовало и было невидимо — три захода подряд.
Расхождение, названное заранее, закрыто тем же заходом. Сужение отрезка у свидетеля переносит имена границы (сПодДлиной), а эталон строил отрезок заново и имена терял; теперь переносит и он («С границами как у»). Названо это было, пока оно ничего не значило, и закрыто ровно тогда, когда перестало быть инертным, — сюрпризом оно не всплыло. Расхождения со свидетелем это сегодня НЕ открывает, и вот почему: заслон печати держит доказаны неизменным, а других наблюдаемых выходов у правки нет — диагностики те же, свод тот же. Долг настоящий, но невидимый, и потому назван здесь, а не оставлен на то, что кто-нибудь заметит.
Главное число этой таблицы — ноль. Строгого сравнения н меньше (длина эл) в корпусе не написано НИ РАЗУ: 14 мест, где длина в сторож всё-таки попала, написаны нестрого (не больше) — и это ровно та форма, которая верна при базе 1 и ложна при базе 0. Отсюда заказ, и он один: база номера обязана стать свойством ПРОГРАММЫ (объявлением модуля), а не ключом запуска, — тогда те же 14 мест доказываются нестрогой границей без единого нового правила.
База номера стала свойством ПРОГРАММЫ, и это взяло 5 мест из 14
Что было. Число базы жило в options и читалось НЕЗАВИСИМО одиннадцатью местами: рантаймом (flang/self/builtins.flang), интерпретатором, восемью печатями и командной строкой. Проверка типов не читала его вовсе — и потому доказывала 1 ≤ н < длина, то есть условие, верное при ОБЕИХ базах. Нестрогая граница н не больше (длина эл) при этом не доказывала ничего, а написана в корпусе именно она: 14 мест.
Что стало. База едет НА ПРОГРАММЕ (базаНомера), и её читают все — включая проверку типов (ctx.базаНомера). Верхняя граница движется вместе с базой: at = н − база < длина значит н < длина + база, то есть при базе 0 верх строгий, при базе 1 — нестрогий. Отсюда два поля вместо одного: подДлиной (строгое знание, годится всегда) и неДлиннее (нестрогое, годится только при базе 1). Решает между ними не место записи, а вГраницах, которому база известна.
Со старым корпусом не происходит НИЧЕГО, и это проверяется, а не обещается. Отсутствие поля значит единицу — то самое умолчание, при котором написан весь сегодняшний корпус. Ни одна написанная программа не меняет смысла ни на одно значение: свод стоит на 279 у 195, побайтовая сверка печати зелена, emit-guard чист.
Слово в языке: элемент начиная с 0
Переходный уклад закрыт, и заказ на слово выполнен. Вчера объявлением служил ключ --index-base; сегодня базу объявляет АВТОР у левого края, рядом со свойство и требует:
модуль «М»
элемент начиная с 0
Нового слова не заведено ни одного, и это правило, а не удача. элемент (item) и начиная с (startingWith) стоят в таблице поверхностей с самого начала и оба открыты на ВСЕХ ЧЕТЫРЁХ: элемент/item/ero/元素 и начиная с/starting with/komencante per/起始于. Из 149 понятий таблицы на всех четырёх открыто 132; дыр 17, и десять из них — слой доказательства (требует…следовательно доказано и убывает). Восемнадцатой здесь не появилось, а появиться могла бы: своё слово пришлось бы заводить четырежды или открывать дыру в словаре, где её быть не должно.
Выбор мерился корпусом, а не вкусом. Правило то же, каким выше отвергнуто по операции: нового слова не заводим, пока хватает старого, а мерой служит занятость. элемент начиная с — ноль вхождений в 751 файле .flang (bootstrap/flang run-script word:occupancy -- "элемент начиная с"), и элемент у левого края сегодня даёт ошибку разбора («этим словом объявление не начинается»). Значит поток токенов ни одной написанной программы от записи не меняется. Тем же приёмом и по той же мере у левого края завели требует.
Умолчание — ЕДИНИЦА, и оно измерено, а не обещано. У молчащей программы ключа базаНомера в AST не появляется ВОВСЕ, и ключ этот последний из последних — байты канонического JSON всего сегодняшнего корпуса те же. Побайтовая сверка печати во все восемь целей зелена, emit-guard чист.
Отказов три, и все три названы (FLANG_PARSE): база не 0 и не 1; база не число; два объявления в одной программе — порядок строк не вправе решать, какое из двух чисел настоящее.
Ключ против объявления: считаем ПО ОБЪЯВЛЕННОМУ. Программа, объявившая базу 0, при --index-base 1 считает по нулю — и в вычислении, и во всех восьми печатях. Отказывать здесь было бы неверно: база — свойство ПРОГРАММЫ, как имя модуля, и ключ, способный его переиграть, вернул бы ровно то, что эта работа убрала, — два источника одного числа. Тихого расхождения при этом не возникает: программа считает то, что в ней написано, при любом ключе. Молчащая программа ключу по-прежнему подчиняется — переходный уклад для файлов без объявления.
ДЫРА, НАЙДЕННАЯ ПО ДОРОГЕ, И ОНА НЕ МОЯ. «Молчащая программа ключу подчиняется» верно у ВЫЧИСЛИТЕЛЯ (evaluate(программа, …, { indexBase: 0 })) и у ПЕЧАТИ (flang emit … --index-base 0 даёт $INDEX_BASE = 0), но НЕ у команды flang run: ключ туда не доезжает вовсе, и молчащая программа считается по единице, что бы ни стояло в командной строке. Проверено прогоном на коммите слияния — было ровно так же, значит правка это не открыла, а обнажила: пока объявления в языке не было, единственным его носителем был ключ, и такая дыра означала бы «объявить базу нельзя». Теперь носитель есть, и дыра стоит на переходном ключе, которому жить недолго. Названа здесь, потому что заказ прямо запрещает молчание: программа не должна ТИХО посчитать другое — а тут тихо считается ПРЕЖНЕЕ, что тоже молчание.
ПОПРАВКА К СОБСТВЕННОМУ ЧИСЛУ, и она в сторону меньшего. Сперва было записано «взялось 5 из 14» — то есть будто нестрогая граница сняла пять мест. Это неверно, и проверено прямо: из мест, где нестрогое сравнение говорит РОВНО про эту пару имён, не снялось НИ ОДНО (10 мест, все на месте). Пятёрка пришла с другой дороги — с ЧИСЛОВОЙ: при базе 1 верх смягчается до верх ≤ длинаНиз, и это сняло четыре места с литеральным номером у элемент, одно у символ и одно с посчитанным верхом у имени. Именная нестрогая граница по корпусу по-прежнему не сняла ничего; заработала она только на тестах.
Почему нестрогая граница не взяла ни одного места, и почему «одно слово на функцию» это НЕ чинит:
| причина | мест | где |
|---|---|---|
номер объявлен число, а не неотрицательное/целое | 7 | core/parser.flang («Слово», «Имя по номеру»), self/bounded.flang, self/interpret.flang (две формы), self/monoid.flang, self/parser.flang, self/setoid.flang |
номер связан пусть, а не параметр | 2 | stdlib/lists.flang «Поиск в диапазоне», docs/zamer2/10-binary-search.flang |
Первая причина — не изъян правила, а более слабое объявление, чем нужно самому коду: рантайм требует ЦЕЛОГО (expectInteger), а число целым не является, и н больше 0 у дробного даёт н ≥ 0, а не н ≥ 1.
«Лечится одним словом на функцию» — тоже моя оценка, и она тоже оказалась неверной. Попытка сделана целиком: восемь объявлений переписаны с число на неотрицательное, и все восемь файлов покраснели у ВЫЗЫВАЮЩИХ одним и тем же отказом — аргумент «номер»: ожидался неотрицательное, получен число. Причина одна и она глубже объявлений: неотрицательное не замкнут по сложению, и это проверяется тремя строками —
тотальная функция «Ш» принимает н: неотрицательное возвращает неотрицательное н плюс 1 → функция «Ш» объявлена как неотрицательное, а тело даёт число
потому что сумма может выйти за точную сетку (отрезок, ТОЧНЫЙ_ПОТОЛОК). Все восемь номеров приходят от счётчиков («сбор».«номер» плюс 1 в свёртке) либо от другого число, и протащить неотрицательное вверх нельзя ни на одном из них. Объявления возвращены к число.
Заказ отсюда следует другой, и он дороже: нужен ЦЕЛЫЙ тип, замкнутый по сложению, — то есть отрезок без верхнего края, у которого +1 остаётся целым. Сегодня такого нет: за ТОЧНЫЙ_ПОТОЛОК целость теряется вместе с точностью, и это решение с измеренной ценой, а не упущение.
Всего по классу доказано анализом 7 мест из 214 (было 2). Разделом ниже это число стало 18 из 213 — и стало корпусной правкой, а не новым правилом.
Заслон печати, и он условие честности числа. Отметка доказана — договор с печатью: свод считает помеченное место НЕ проверяемым во время работы (flang/self/failures.flang). Доказанного пути у элемент и символ в восьми целях нет, поэтому отметки они не получают — иначе свод уменьшился бы, а сторож в напечатанном коде остался бы стоять, и число стало бы неправдой на эту разницу. Заслон читает тот же список СНИМАЮТСЯ_НЕПУСТОТОЙ, по которому помощникФормы выбирает имя помощника, и откроется сам одной строкой в нём. Что доказал АНАЛИЗ, отдаётся отдельным именем (доказаныАнализом), чтобы долг печати называли числом.
Корпус СПРОСИЛИ: 7 мест стало 18, и потолок класса оказался не 214, а 24
Прежний заход кончился словами «механизм есть, читатель есть, эталон есть — язык просто не просят». Здесь язык спросили: границу номера ВПИСАЛИ В КОРПУС.
Прежняя таблица причин описывала ТЕКСТ, а отказ приходит раньше текста. Причины считались по исходнику («сравнение нестрогое», «сравнения с длиной нет вовсе», «номер сравнивается только с литералом»), а вГраницах до сравнения может не дойти вовсе. Замер прямо в точке отказа даёт другую раскладку — 213 мест класса в собственных функциях файлов корпуса:
где именно отказывает вГраницах | мест |
|---|---|
подстрока — правила нет, до вГраницах дело не доходит | 74 |
| номер НЕ ЦЕЛЫЙ — отказ первой же строкой, сторож не читается | 115 |
| низ номера меньше базы | 11 |
| верх без имени и без числа | 4 |
| контейнер не имя | 2 |
| доказано анализом (на входе захода) | 7 |
115 — это число, которого корпусной правкой не сдвинуть, и это проверено прогоном, а не выведено. Одно и то же тело с одним и тем же сторожем:
принимает эл: список числа, н: число требует «низ» н не меньше 1 требует «верх» н меньше (длина эл) элемент н в эл → доказано 0
принимает эл: список числа, н: неотрицательное если н больше 0 и притом н меньше (длина эл) то элемент н в эл → доказано 1
Идеальная пара требует при число не доказывает НИЧЕГО: вГраницах отказывает на целыйЛи прежде, чем посмотреть на границы. Значит приказ «напишите строгое сравнение на 55 местах» дал бы РОВНО НОЛЬ, и прежняя таблица звала именно туда. Из 115 у 84 номер — ИМЯ (параметр или пусть типа число), у 31 — выражение; ни одно ни другое сторожем не лечится.
Потолок класса для корпусной правки равен 24 = 213 − 115 − 74. Взято из них 18, то есть три четверти достижимого; было 7.
требует заплатить нечем, и это тоже измерено. docs/examples/measure/natural.flang, «Сумма до места» получила честное требует «место не за концом списка» место не больше (длина элементы). Оба места вызова из двух покраснели FLANG_PRECONDITION_CALL, и внешний — на цели (длина элементы) не больше (длина элементы): у ядра три правила («не меньше 0», «не больше конечного литерала», «равно») плюс «цель есть допущение», и РЕФЛЕКСИВНОСТИ среди них нет. требует отменено, взято сторожем.
Порядок ветвей занят тотальностью. Свести обе половины в одно условие и перевернуть ветви нельзя: FLANG_NOT_TOTAL — проверка убывания читает ФОРМУ сторожа («если место не больше 0»), а не его смысл. Отсюда единственный безопасный узор: внешний сторож не трогать, границу вкладывать в его иначе.
Три узора правки, и все три — корпус, а не оснастка.
- Длина, связанная именем.
элемент (длина X) в X— самая частая форма «последнего элемента»; механизм её не берёт, потому чтодлинавозвращает ПОСТОЯННЫЙнеотрицательное[0, 2^53−1] и не помнит, чьей длиной она была. Связать длину именем и наложитьконец больше 0 и притом конец не больше (длина X). Вторая половина при этом ТАВТОЛОГИЯ, и написана она только затем, что равенствоконецидлина Xпроверке типов неизвестно. - Нижняя граница длины литералом.
если (длина ряд) не меньше 3даётдлинаНиз= 3, иэлемент 2 в ряд,элемент 3 в ряддоказываются ЧИСЛОМ. Два места одной строкой — самая дешёвая правка класса. - Вложенный верх у счёта вниз.
если место не больше (длина эл)внутрииначеу существующего сторожа тотальности.
Смысл изменён ровно на недостижимом пути, и сказано это числом. Восемь правок добавляют ветвь там, где сегодня был бы отказ рантайма; ни один пример не сдвинулся ни на одно значение (505 проверок leetcode, measure, index-bounds, nonempty зелены), emit-guard чист, сторож чисел на прежних 18, свод стоит на 279 у 195 — и стоять обязан: заслон печати держит элемент и символ без отметки доказана.
КРАСНЫЕ СВЕРЕНЫ ИМЕНАМИ, И ЗАЯВЛЕННАЯ ЧИСТАЯ БАЗА ОКАЗАЛАСЬ ЗАНИЖЕНА. Полный прогон дал 4312 / 4293 / 19 при заявленных 4312 / 4294 / 18. Все двенадцать файлов, несущих красные, прогнаны на самой базе (de27451, отдельное дерево): там их 20, у меня 19. Diff именами: НОВЫХ НОЛЬ, исчез ровно один — «страница „Что изменилось“ совпадает с историей репозитория». То есть заявленная восемнадцатка устарела: два красных приехали последними коммитами самой базы, и один из них — прямое следствие правки self/types.flang («непустота доказана…», голова 337 против 301). Ни один красный не принадлежит правке корпуса.
Заказ отсюда следует ОДИН и он не про корпус. Пока неотрицательное и целое не замкнуты по сложению, 115 мест недоступны никакому тексту программы. Второй, дешёвый и независимый: длина X обязана возвращать число, помнящее X, — низом длинаНиз(X) и именем в неДлиннее. Одна эта пара строк взяла бы форму «последний элемент» без единой тавтологии в корпусе.
ВТОРОЙ ЗАКАЗ ВЫПОЛНЕН, И ОЦЕНКА СОШЛАСЬ НАПОЛОВИНУ — ЗАМЕР НИЖЕ. Правка и правда в ДВЕ строки (builtinType, случай «длина»): отрезок(длинаНиз(X), 2^53−1, true) низом и X в неДлиннее именем. Форма «последний элемент» взята: элемент (длина X) в X доказывается при базе 1 ровно тогда, когда о X доказана непустота. Три тавтологии из корпуса убраны (062, 064, 072) — в 064:132 и 516:124 похожая строка ОСТАВЛЕНА нарочно: там конец есть длина сетка и длина буквы, а сторож про длина таблица, и это разные имена.
Чего оценка не назвала — число. Замер в чистом каталоге (git archive в пустой) по 186 файлам examples, stdlib, self, core: 213 мест класса номера, доказано было 18, стало 19. Открылось РОВНО ОДНО новое место (015-3sum); четыре места узора (062, 064, 072, 516) уже стояли доказанными — платой за них и была тавтология. То есть ход куплен не приростом, а ЧЕСТНОСТЬЮ сторожей, и сказать это числом важнее, чем округлить его вверх.
Подделки отвергнуты каждая своим тестом (index-bounds, 34 из 34): без непустоты место остаётся сторожем; длина др при взятии из эл не в счёт; (длина эл) минус 1 знания не несёт; длина (добавить 0 к эл) имени не даёт; при базе номера 0 та же запись указывает на один за концом и НЕ доказана.
РЕФЛЕКСИВНОСТЬ: ПРАВИЛО УЖЕ БЫЛО, И УЖЕ ТЕОРЕМОЙ. Заказ звучал как «у ядра три правила плюс „цель есть допущение“, рефлексивности среди них нет». Прогон до работы это опроверг: правило 4 «порядок по построению» несёт случай ТОЖДЕСТВО, и написан он не как одинаковы(левое, правое) → да, а как если (одинаковы левое правое) то (неотрицательно правое …).
ПОСЫЛКА, НАЗВАННАЯ ВСЛУХ: о границе ДОКАЗАНО «не меньше 0» правилом 1.
Теоремой ход делает она. Е не больше Е в IEEE-754 и правда не теорема — на не число сравнение ложно. Но не число не меньше 0 ложно тоже, значит всё, о чём не меньше 0 ВЫВЕДЕНО, не есть не число; на носителе без NaN порядок рефлексивен. Посылка доказывается, а не принимается: АКСИОМЫ пусты.
Главная подделка захода отвергнута: требует «под порогом» х не больше порог при х: число и зове «Взять» от знач и знач остаётся КРАСНОЙ — о число низа не обещает никто.
Чего узкой формы не хватает — числом. У «Сумма до места» мест вызова ДВА, снимается ОДНО: внешнее ((длина элементы) не больше (длина элементы)). Рекурсивное — цель (место минус 1) не больше (длина элементы) — не закрылось бы никакой рефлексивностью: вычитания у правила порядка нет и не будет. Вторая граница тоже названа числом: снятьПредусловия отдаёт свести ПУСТОЙ список объявлений, поэтому имя, объявленное неотрицательное, посылкой не служит — ноль мест из одного на пробе. Корпус спрошен: мест вызова под предусловием восемь, снято восемь, красных ноль; новых мест заказ корпусу не открывает.
ТРЕТЬЯ ФОРМА КЛАССА ВЗЯТА, И ВЗЯТА ОНА ДВУМЯ ВЕТКАМИ СРАЗУ. Правило границ подстрока писалось независимо в двух местах: на work/substring-rule (четыре факта и третье поле неБольше — отношение двух имён ЧИСЕЛ) и здесь. Разведка этого не увидела и записала «правила нет вовсе»; про ветку это было верно, про проект — нет, и ошибка стоила половины хода (docs/zettel/vyvod-na-vetke-ne-vyvod-o-dereve.md). Ветки слиты, правило взято оттуда, сюда добавлены две дороги, которых там не было.
ДОГОВОР — ЧЕТЫРЕ ФАКТА (flang/self/builtins.flang, «подстрока»; при длине L и базе b): Ф1 начало ≥ b; Ф2 начало ≤ L + b; Ф3 конец ≤ L (базой НЕ сдвигается); Ф4 конец ≥ начало − b. Ф1 — число против числа; Ф2 и Ф3 — число против ДЛИНЫ ИМЕНИ, и оба поля (подДлиной, неДлиннее) годятся при обеих базах, потому что в Ф3 потолок и есть сама длина, а в Ф2 он выше её на неотрицательное b. Ф4 — единственный факт другого рода, и дорог у него ТРИ: числа, отношение двух имён (неБольше) и РАВЕНСТВО ИСТОЧНИКА.
ПОЛОВИНЫ ОДНОГО КЛАССА РАЗЪЕХАЛИСЬ ПО ВЕТКАМ И ПОРОЗНЬ УПИРАЛИСЬ В ОДНО. Та ветка умела спрашивать имя контейнера, но длина X возвращала у неё постоянный неотрицательное, не помнящий X; эта ветка научила длина X помнить X (неДлиннее), но правила подстрока не имела. Препятствие у обеих было одно и то же и названо числом ещё там: 26 мест из 82 упираются в Ф3, потому что в корпусе конец пишется ВЫРАЖЕНИЕМ (длина «текст»), а не именем.
СЛИЯНИЕ ЭТО ПРЕПЯТСТВИЕ СНЯЛО НАПОЛОВИНУ, И ЦИФРА ЗАМЕРЕНА, А НЕ ОЦЕНЕНА. Разбор AST по 186 файлам, три дерева одним измерителем:
| состояние | всего мест | элемент | символ | подстрока |
|---|---|---|---|---|
правило подстрока есть, длина НЕ помнит (та ветка) | 21 | 14 | 1 | 6 |
СЛИЯНИЕ: правило + длина X помнит X | 37 | 18 | 1 | 18 |
| плюс две дороги этой ветки | 52 | 18 | 1 | 33 |
Мест класса в корпусе 212 (элемент 136, символ 2, подстрока 74). Слияние дало +16 (у подстрока 6 → 18, у элемент 14 → 18), правки этой ветки сверх слияния — +15 (18 → 33). Из 26 мест Ф3 слияние открыло РОВНО 12, правки ветки довели счёт до 21, а на самом Ф3 из них стоят 2 (self/parser.flang:915 и :946 — там конец не про длину строки вовсе). Именная дорога Ф4 (неБольше) не закрыла в корпусе ни одного места ни до слияния, ни после — это измерение, а не догадка, и оно совпало с тем, что записано на той ветке.
Тонкость, которую стоило проверить, а не подгонять. Ф3 читает ОБА поля — и строгое подДлиной, и нестрогое неДлиннее, — поэтому нестрогое знание, которое кладёт длина X, годится ему без единой правки. Это законно ровно потому, что Ф3 базой не сдвигается: конец ≤ длина — то самое, что говорит неДлиннее, при любой базе. Однострочной правки не потребовалось: всё дал сам факт слияния.
ДВЕ ДОРОГИ, ДОБАВЛЕННЫЕ ЗДЕСЬ.
Равенство источника у Ф4 (+6 мест): подстрока X с к по к даёт конец = начало, а к ≥ к − b верно при b ≥ 0. Именем это не берётся — неБольше пишет только сужение, а условия к не больше к никто не пишет; отрезком тоже — низ и верх у к разные. ПОСЫЛКА НАЗВАНА: «Е не больше Е» в IEEE-754 не теорема, на NaN оно ложно, а NaN достижим изнутри языка (0 делить на 0); держит её проверка целости обоих концов (целыйНомер), и подделка на это стоит поимённо.
Именная граница переживает ВЫЧИТАНИЕ ПОЛОЖИТЕЛЬНОГО (+9 мест): если а ≤ длина X, а д ≥ 1, то а − д < длина X, то есть нестрогое знание становится СТРОГИМ. Про СЛОЖЕНИЕ прежняя запись выше остаётся верной; про вычитание — нет, и это правило написано одной ветвью (арифметика, случай sub) с двумя проверяемыми посылками: д ≥ 1 и ЦЕЛОСТЬ ИТОГА (разность двух целых с точной сетки считается точно; без второй посылки округление могло бы вернуть значение вверх у самого края). Взято потому, что подстрока X с 1 по ((длина X) минус 1) написана в корпусе одиннадцать раз.
ЧТО ОСТАЛОСЬ, СЧЁТОМ ПО 76 ВЫЗОВАМ ПРАВИЛА (74 места, два посещаются дважды): доказано 33; начало/конец не целые — 23 (тот же число вместо неотрицательное, что у 114 мест элемент, и сторожем это не лечится); Ф2 — 6; Ф1 — 4; Ф4 — 4; строка не строка — 4; Ф3 — 2. Самое большое препятствие класса перестало быть третьим фактом и снова стало ТИПОМ.
Свод не сдвинулся: 279 у 195 осталось 279 у 195. подстрока в СНИМАЮТСЯ_НЕПУСТОТОЙ нет, значит заслон печати не пропускает ни одну из 33 отметок в доказаны; выросло только доказаныАнализом. Пока восемь целей не умеют печатать границы отрезка без сторожа, сдвинуть свод значило бы соврать ровно на эту разницу.
Подделок в файле границ 72 проверки, из них подделок восемнадцать (шесть пришли с той ветки, двенадцать добавлены здесь), и все отвергнуты: чужое имя в Ф3 и оно же при базе 0; конец без верха; отрезок вывернут (с 3 по 1) при законном соседе с 2 по 1; А ≤ Б между двумя именами; источник только похож (с (длина т) по (длина др)); NaN; дробный номер; чужое имя под вычитанием; сложение вместо вычитания; вычитание нуля; вычитание из числа без знания об имени. Подделка той ветки про имя списка в наборе чисел ПЕРЕПИСАНА: с двумя одинаковыми концами она перестала изолировать своё правило, как только у Ф4 появилась третья дорога, — концы разведены на два разных имени, и зубы вернулись.
Сверх подделок правило сверено с САМИМ РАНТАЙМОМ на сетке границ: 968 входов (пустая строка, ноль, минус ноль, дробное, NaN, потолок точной сетки, номера левее начала и правее конца, обе базы), доказано 62, отказов рантайма на них ноль. Изъятие Ф3 красит сетку и ещё пять проверок — то есть сетка стережёт, а не сопровождает.
Три правила снятия, и все три уже были у ядра. Аргументы подставляются в предусловие вызываемого — получается инстанция. Дальше: сведением (те же два правила flang/self/proof-kernel.flang при фактах вызывающего — его собственных предусловиях), вычислением (инстанция замкнута — то же правило, каким закрывается база индукции) и никак, с отказом, называющим вызов, предусловие и что было известно. Новых правил ноль, аксиом по-прежнему ноль.
Что это дало доказательству. Ближайшая названная граница ядра сдвинулась: посылка индукции, у которой связано свободное имя, раньше не закрывалась ничем — примером нельзя (значений бесконечно много), предположением индукции нельзя (частей нет). Предусловие её закрывает; так доказана «Высота от дна» в flang/proof/examples/precondition.flang. Появилось и доказательство вне индукции: цель с подставленным телом функции сводится к предусловиям, и это работает там, где объявленной суммы нет вовсе.
Чего у предусловия нет. Нет результат: результата до вызова не существует. Нет для всех: квантор при постусловии называет параметр для индукции, а предусловие снимается у каждого вызова по отдельности. Нет условия ветвления вызывающего как факта: собирать путевые условия значило бы завести анализ, которого у языка нет ни одного, — и это названо в отказе, а не скрыто. Роль гипотезы в теореме по-прежнему несёт дано; требует несёт гипотезу в функции, и это разные места.
Гипотеза теоремы не добавляет допущений. Каждая строка дано <утверждение> обязана выводиться из того, что утверждение допускает само: его таких что, предусловий требует функции и объявленных типов переменных. Ядро сводит гипотезу к ним теми же правилами, что шаг по предположению. Не свелась — утверждение не доказано: отказ FLANG_PROOF_CLAIM_MISMATCH «гипотеза теоремы не является допущением утверждения». Иначе теорема с дано н больше 0 доказывала бы постусловие, ложное на нуле.
Предусловие обязано говорить о входе. Замкнутое предусловие отвергается типизатором (FLANG_PRECONDITION): оно либо истинно всегда — и тогда обязывает вызывающих ни к чему, — либо ложно всегда, и тогда телу досталась бы допущением ложь, из которой выводится что угодно. Граница проверки названа: требует «ложь» н больше н упоминает параметр и всё равно невыполнимо; решателя у языка нет. Ложь при этом не выходит наружу — снять такое условие не сможет ни один вызывающий, и на границе оно тоже не пройдёт.
2. Обязательства (flang/self/obligations.flang)
Постусловие написано у функции, теорема — отдельным объявлением, связаны они только именем. Ядру нельзя дать ни то, ни другое по отдельности: терм проверяется ОТНОСИТЕЛЬНО цели, а цель написана у функции. Значит между ними стоит слой, который сводит их в один объект — обязательство (verification condition).
Обязательство — это ДАННЫЕ, а не текст: цель, квантор, гипотезы, связка результата, размер сетки примеров и предложенный терм. Ни одной строки для человека, кроме id. Передавать ядру текст значило бы третий разбор одного и того же после лексера и парсера.
Три источника: постусловие, объявленная мера, теорема. Теорема не создаёт обязательство, а закрывает его. Теорема, которая ничего не закрывает, — отказ: доказано было бы неизвестно что.
Обязательство меры приходит уже закрытым, и закрыл его не автор: убывание доказал анализ завершаемости обходом графа вызовов до всякого доказательства. Ядру там делать нечего, и притворяться, будто есть, значило бы поставить в цепочку доверия шаг, которого нет.
Отказы этого слоя: теорема без цели, теорема не о том, что обещает функция (сличение синтаксическое — ядро не решает, что два разных утверждения означают одно и то же), два постусловия на одно имя, две теоремы на одно постусловие, переменная теоремы не параметр функции, незакрытое доказательство.
3. Ядро (flang/self/proofterm.flang)
Ядро ничего не ищет. Ни перебора правил, ни подбора подстановки, ни решателя. Это не экономия: поиск и есть то, из-за чего проверяльщику нельзя доверять, не прочитав его целиком, а прочитать целиком можно только маленький. Шаг без названного факта отвергается с указанием, что назвать.
Индукция не заведена отдельным понятием. Индуктивный тип есть начальная алгебра, и её универсальное свойство даёт сразу и свёртку (она в языке с самого начала — свёртка … начиная с …), и принцип индукции. Поэтому принцип здесь читается с объявления суммы: случаев ровно столько, сколько конструкторов, и в рекурсивном случае доступно предположение о части значения. Ядро сличает случаи с объявлением — точным равенством множеств, а не покрытием: исчерпывающность разбор считает типизатор по тем же образцам, и разреши здесь лишний случай, два слоя стали бы отвечать на один вопрос по-разному.
3а. Принцип индукции — ТЕРМ, а не правило (flang/self/proof-initial.flang)
Принцип не вписан в ядро ни для списка, ни для дерева, ни для перечисления. Он порождается по объявлению суммы и порождается как ДАННЫЕ, которые ядро потом сворачивает тем же «слабейшее побеждает», каким сворачивает всё остальное. Разница не стилистическая: правило на JavaScript пришлось бы читать целиком, чтобы ему поверить, а терм ядро проверяет.
Сигнатура функтора читается с объявления буквально:
тип «Дерево чисел»
вариант «Пустое»
вариант «Развилка» содержит значение: число, меньшие: «Дерево чисел», большие: «Дерево чисел»
F(X) = 1 + (число × X × X)
Сумма по вариантам, произведение по полям, X на месте каждого поля, чей тип — сам объявляемый тип. Выбирать не из чего: это то же объявление, переписанное другими знаками. Отсюда принцип:
- на каждый конструктор — посылка;
- у посылки столько допущений, сколько у конструктора полей типа самой суммы;
- заключение посылки — цель, в которой переменная индукции стала конструктором, а
результат— телом ветвиразборпо этому варианту.
Посылка без допущений называется базой, с допущениями — шагом. Это не два правила: у базы X встретился в посылке ноль раз, у шага — больше нуля. Поэтому перечисление («Светофор») доказывается тем же самым принципом, что дерево, а не «конечным исчерпанием как особым случаем». Проверено тем, что вердикт светофора теперь говорит «база 3 случая, шаг при допущении на частях (0 случаев)».
Граница поля X названа прямо: меньшие: «Дерево чисел» — место X, а дети: список «Дерево чисел» — нет, потому что за списком стоит другой функтор, и допущение о его элементах — другое допущение. Это та же граница, что у монады (flang/self/monad.flang: параметр обязан стоять в поле ЦЕЛИКОМ).
3а-бис. Второе объявление: у ВСТРОЕННОГО списка тоже есть начальная алгебра
Объявление, с которого читается принцип, бывает двух видов, и оба читаются. Первое пишет автор словами тип … вариант …. Второе не пишет никто — и оно всё равно есть: у встроенного список Э объявления в ПРОГРАММЕ нет, но есть в самом ЯЗЫКЕ, и лежит оно в трёх местах, каждое из которых язык уже считает сегодня.
| факт | кто его говорит | что из него следует |
|---|---|---|
конструкторов ровно два — пусто и голова и хвост | типизатор: разбор списка исчерпан тогда и только тогда, когда покрыты оба (flang/self/types.flang) | посылок у принципа ровно две |
| голова: Э, хвост: список Э | тот же типизатор (bindPattern, случай cons) | F(X) = 1 + (Э × X); место X — хвост |
| хвост — часть значения, строго меньшая целого | анализ завершаемости (flang/self/totality.flang) | допущение о хвосте законно |
Третий факт — тот самый, на котором стоит обещание «тотальная, структурой» у 212 функций корпуса. То есть допущение индукции о хвосте законно ровно постольку, поскольку законно это обещание: довод один и тот же, и второго здесь не заведено.
Это порождение, а не постулат. Аксиома — утверждение, принятое без доказательства, и её нельзя проверить; здесь же не утверждается ничего нового — три действующих факта переписаны в ту же форму, в какой автор пишет объявленную сумму (flang/self/proof-initial.flang). Список АКСИОМЫ остался пуст. Дальше ядро не различает, откуда объявление пришло: сигнатура функтора, посылки, точное покрытие и свёртка принципа у них общие, и правило «место X» — одно: поле, чей тип есть сам объявляемый тип. Способа узнать «тот же тип» два только потому, что типы записаны двумя способами: у суммы — именем (хвост: «Стопка»), у списка — целиком (хвост: список Э).
Строке алгебра дана тем же чтением (c14cd749e, 20 августа 2026; прежде здесь стоял отказ «голова строки — тоже строка», написанный по памяти, а не по типизатору). Типизатор исчерпывает разбор строки теми же двумя образцами и связывает голову типом «строка длиной 1», хвост — типом «строка» («Связать части строки» в types.flang); значит место X — только хвост, голова — коэффициент («Объявление строки» и «Тип знака» в proof-initial.flang). Голова обязана остаться коэффициентом: у строки из одного знака она равна целому, и допущение о ней было бы допущением о доказываемом. Свёртка по знакам строки — свёртка (разложить Т на символы) … — получает принцип свёртки правилом Инд4 (задача 7359, раздел «Что дальше» п.7).
3б. Шаг индукции: чем он выведен (ядро — flang/self/proof-kernel.flang)
⚠ ЧИСЛО В ЭТОМ РАЗДЕЛЕ УСТАРЕЛО, И ТАБЛИЦА НИЖЕ БЕДНЕЕ ЯДРА. Раздел написан, когда правил было три; сегодня их восемь, а перечень внутри каждого длиннее написанного здесь на 28 умений. Полный список — раздел «Восемь правил ядра и 28 умений, которых в описании не было»; сам список —
flang/proof/tables/abilities.tsv, улики —flang/proof/map/abilities.flang, сторож —bootstrap/flang io scripts/kernel-abilities-guard.flang. Раздел оставлен как летопись.Ссылка на
flang/proof/reduce.mjsтоже мертва: реализации на JavaScript в дереве нет, ядро живёт вflang/self/proof-kernel.flang.
по предположению больше не «объявляет допущение известным». Оно обязано свести заключение посылки к допущениям, и пока сведение не прошло — шаг не выведен. Правил сведения на день написания раздела было три, и все три печатаются в отказе поимённо:
| правило | вид цели | чем выводится |
|---|---|---|
| неотрицательность по построению | Е не меньше 0 | Е собрано из неотрицательного: литерал ≥ 0, терм с допущением, имя, объявленное типом с дном (неотрицательное, вес), встроенная форма с объявленным от нуля результатом, их сумма, выбор из них, их произведение — двумя посылками на выбор: либо о КАЖДОМ сомножителе известны обе границы отрезка [0, конечное], либо ОДИН сомножитель лежит в (0, конечное], и тогда второму хватает дна, свёртка с неотрицательным началом и сохраняющим знак шагом |
| ограниченность точным потолком по построению | Е не больше П, П — конечный литерал | Е собрано из ограниченного сверху: литерал ≤ П, терм с допущением «не больше» той же или меньшей границы, имя, объявленное типом неотрицательное (при П ≥ 2⁵³−1), выбор из них, разность с ограниченным уменьшаемым и неотрицательным вычитаемым. Суммы здесь нет |
| тождество после переписки допущением | А равно Б | после переписки допущениями (одно применение на допущение) стороны совпадают — но НЕ синтаксически: плюс и умножить на сличаются с точностью до порядка соседей ОДНОГО узла (У1, У2), длина восьми встроенных форм переписывается равным выражением (У4—У7), и берётся свёртка, растущая ровно на один (У3) |
Разница между первой и второй строкой — не описка и не пробел: сумма стоит в первом правиле и не стоит во втором, и ровно в этом состоит различие между дном и потолком отрезка неотрицательное.
И ЧЕТВЁРТЫЙ ХОД, КОТОРЫЙ НЕ ЯВЛЯЕТСЯ ЧЕТВЁРТЫМ ПРАВИЛОМ (раздел 3б-квинт): цель, в которой не осталось ни одного свободного имени, вычисляется. Спрашивается он ПОСЛЕ всех трёх и только при их отказе.
Перед всеми тремя стоит нормализация, и она тоже закрыта списком — теперь из четырёх переписок: пусть разворачивается, разбор известного конструктора выбирает ветвь, если с уже вычисленным условием выбирает ветвь, и — четвёртой — разворачивается определение функции (раздел 3б-бис).
Останов виден глазами: каждая переписка либо уменьшает дерево, либо подставляет уже нормализованное поддерево. У четвёртой останов держится иначе, и об этом сказано прямо ниже: у неё есть названный предел, и это единственное место ядра, где счёт ведётся числом.
3б-бис. Четвёртая переписка: развёртка определения
Здесь стояло: «вызов функции не разворачивается никогда — у «Высота» от х при неизвестном х нормальной формы нет». Первая половина была решением, вторая — доводом, и довод оказался шире решения: у «Высота» от х нормальной формы правда нет, а у «Высота» от (Звено с головой г и хвостом х) она есть, и написана она автором в теле функции.
Ограничение стоило двух утверждений корпуса, и обе улики измерены прогоном, а не вспомнены:
- «высота копии равна высоте оригинала» (
flang/proof/examples/stack.flang) — шаг требует знать, чему равна «Высота» НА КОНСТРУКТОРЕ, и без этого стороны равенства остаются разными термами. Улика записана владельцем языка словами ровно так же; - «глубина дерева неотрицательна» (
flang/stdlib/tree.flang) — шаг упирается в«Глубже» от двух допущений, и о результате чужой функции ядро не знало ничего, хотя тело её написано тремя строками ниже. Улика стояла исполняемым заказом в проверке утверждений набора.
Развёртка — не пятое правило, а переписка, и это не формальность. Решающих правил по-прежнему два, и оба утверждения выше закрыты СТАРЫМИ правилами: первое — «тождеством после переписки допущением», второе — «неотрицательностью по построению». Развёртка не добавляет знания. «Ф» от а и тело «Ф» с подставленным а — одно выражение, записанное дважды: так определил автор, и типизатор эту подпись уже проверил. Ядро читает написанное — ровно так же, как читает ветвь разбор по известному конструктору. Список АКСИОМЫ не пополнился ни на строку.
Бесконечная развёртка рекурсии — главная опасность этой переписки, и потому у неё четыре ограничителя. Каждый назван, каждый стоит отдельной строкой в flang/self/proof-kernel.flang, и у каждого свой тест-изъятие в Проверка ядра:
| # | ограничитель | чем держится |
|---|---|---|
| 1 | только тотальная | обещание завершения дало не ядро — его доказал анализ завершаемости. Подставить тело вместо вызова, который может не досчитать, значило бы говорить о значении, которого нет |
| 2 | два вида вызова, список закрыт | «по конструктору»: тело есть разбор <параметр>, а на его месте стоит КОНСТРУКТОР — ветвь выбирает объявление, конструктор съедается, а рекурсивный вызов в ветви стоит уже на ЧАСТИ (на имени) и второй раз не разворачивается. «Плоское определение»: в теле нет ни одного вызова, значит новых вызовов развёртка не заводит |
| 3 | ПРЕДЕЛ_РАЗВЁРТКИ = 32 | тело вправе конструктор и ПОСТРОИТЬ («Ф» от (вариант «Звено» с хвост равным х)), и тогда витку не будет конца; структурная мера этого не ловит. Достижение предела печатается в отказе свести, а не проходит молча |
| 4 | захват имени | если в развёрнутом теле остался связыватель, связывающий имя, свободное в аргументе, развёртка ОТМЕНЯЕТСЯ. Переименования нет: выдуманное имя не сличить с допущением, а молча переименованная чужая переменная — самая дорогая ошибка проверяльщика |
Ограничитель 4 знает ЧЕТЫРЕ связывателя, и до 16 августа знал три. Список назван в нём прямо, а не выведен, и в этом его цена: подставить в flang/self/proof-initial.flang и нормализация в самом flang/self/proof-kernel.flang научились отобразить/отфильтровать четвёртым связывателем, а сторож захвата остался с прежними тремя — и его собственная строка это утверждала («связывателей в языке три»). Дыра измерена программой, а не рассуждением:
тотальная функция «Сколько равных»
принимает х: число
длина (отфильтровать [1, 2, 3] где эл → эл равно х)
тотальная функция «Проверка»
принимает эл: число
для всех эл обеспечивает «равно трём»
результат равно (длина (отфильтровать [1, 2, 3] где эл → эл равно эл))
«Сколько равных» от эл
Развёртка подставляла эл вызывающей функции ПОД эл фильтра, цель сходилась правилом тождества знак в знак, и ведомость печатала «доказано сведением цели с телом функции … обо ВСЕХ входах». flang run на том же файле отвечал FLANG_PROPERTY на эл = 2: слева считается 0 или 1, справа всегда 3. Улика стоит негативным тестом (проверка непротиворечивости, «ПОДДЕЛКА ЗАХВАТОМ»), рядом с ней — граница с другой стороны (тот же вызов с аргументом, названным иначе, разворачивается и доказывается по-прежнему), и изъятие красит именно её.
Появись пятый связыватель — его придётся вписать в ТРИ места сразу (подставить, шаг, связывает), и это записано здесь, потому что вывести список неоткуда.
Почему предел не может соврать. Развёртка только читает определение, поэтому остановиться раньше — значит доказать МЕНЬШЕ, а не доказать ложное. Ровно поэтому число 32 не входит в контур доверия: от него зависит, докуда доходит честная развёртка, и не зависит ни одно «доказано».
Ядро по-прежнему ничего не ищет. Определения приезжают в свести четвёртым аргументом — данными, наравне с допущениями и объявлениями. Не дали — развёртки нет вовсе, и нормализация та же самая, что была до этой работы; это отдельная проверка («развёртки нет, пока определений не дали»). Так же и в обратную сторону: flang/self/proof-initial.flang нормализует тело функции ради ветвей разбор и определений не даёт нарочно — ветви разбора обязаны читаться с того тела, которое написано.
Что развёртка НЕ берёт, и это её граница, а не недоделка. «Копия» от х при неизвестном х не разворачивается: аргумент — имя, разбор по нему ветви не выберет. «Фибоначчи шагом» от н и 0 и 1 не разворачивается: рекурсия идёт по ЧИСЛУ, конструкторов у неотрицательное нет, а тело не плоское — в нём стоит вызов себя же. Именно поэтому допущения индукции никуда не делись: они и есть то, что известно о вызове, который разворачивать нельзя.
3б-кватер. Сужение по условию если: почему его у ядра НЕТ
Оба правила границ спускаются в если и требуют своего от обеих ветвей, а само условие не читают. Выглядит это упущением, и заказ на него стоял исполняемой строкой: flang/stdlib/numbers.flang «Абсолютное значение» написана как если число меньше 0 то 0 минус число иначе число, и «модуль числа неотрицателен» человеку очевидно. Приём в дереве есть и работает — сузить в flang/self/types.flang (числовые отрезки) и там же сужение по пусто (непустота списка).
Приёма в ядре нет, и решение это измерено, а не выбрано. Замеры стоят исполняемыми в проверке ядра («сужение по условию: цена измерена»), и их три:
| # | что мерялось | ответ |
|---|---|---|
| 1 | ветвь иначе — можно ли читать отрицание условия | НЕТ, вывод был бы ЛОЖНЫМ. В IEEE-754 сравнение с NaN ложно всегда, поэтому из «число меньше 0 не выполнилось» не следует «число не меньше 0»: на NaN ложны обе стороны. «Абсолютному значению» нужна ровно эта ветвь, и утверждение о ней ЛОЖНО — Абсолютное значение от NaN даёт NaN |
| 2 | ветвь то — законная половина приёма | закрывает 0 целей. Функций в корпусе 4009, условий если 3454, читаемых сужением 199, закрыто ноль: идиома корпуса — если <мало> то <база> иначе <работа>, то есть нужный факт всегда в ветви, читать которую нельзя |
| 3 | всё заказанное — обе ветви плюс вычитание в правиле дна | закрывает ровно 1 цель — ту самую «Абсолютное значение», то есть ЛОЖЬ |
NaN здесь не мысленный эксперимент: литерала для него у языка нет, но 0 делить на 0 проходит типизацию без единой диагностики — деление на ноль в flang даёт значение IEEE-754, а не отказ (flang/self/builtins.flang). В проверке вход именно посчитан программой, а не подан аргументом.
Отсюда же ответ про остаток от, которого нет ни в одном правиле. Посылок у него не одна, а две (для дна) и три (для потолка), и все три сняты прогоном (проверка утверждений библиотеки, «границы остаток от сняты прогоном»):
- знак остатка — знак делимого (
-5 остаток от 3= −2), значит нужна неотрицательность делимого; - и его КОНЕЧНОСТЬ:
бесконечность не меньше 0истинно, абесконечность остаток от 3— не число. Одного дна правилу мало; - для потолка
м−1нужна вдобавок целость:2.5 остаток от 3= 2.5, то есть больше, чем м−1 = 2.
Первые две посылки у ядра появились — это вОтрезке из работы над умножением (ветка work/kernel-mul-field), — и правило пишется одним истинным случаем: а остаток от м неотрицателен при а в отрезке [0, конечное] и конечном ненулевом литерале м. Заводить его всё равно не за чем: тот же обход по корпусу показывает 0 новых закрытых целей, а «В кольцо» из hashmap.flang остаётся незакрытой и с ним — конечность там приходит из (значение минус значение) равен 0, то есть формой, которую ядро фактом не читает.
Общее правило, по которому оба приёма отложены, дерево сформулировало раньше: факт, которым никто не пользуется, — доверие, за которое некому отвечать.
3б-трис. Третий источник фактов: объявленный тип аргумента
Здесь стоял разрыв, который читался как мелочь, а стоил целой строки ведомости. Источников известного у правила неотрицательности было два — литерал не меньше нуля и допущение индукции, — и оба приезжают ИЗНУТРИ доказательства. Объявление параметра не приезжало никак: слов тип, types, typeOf в flang/self/proof-kernel.flang было ноль, и это проверялось одной командой (grep -ac "тип\|types\|typeOf" flang/proof/reduce.mjs → 0).
Поэтому постусловие «результат не меньше 0» у функции над двумя неотрицательное стояло в ведомости строкой «сетка 1 значение (примеры функции)»: посчитанным на одном входе из 2⁵³ × 2⁵³, хотя доказуемым одним сложением.
Причина была не в том, что неотрицательно ходит по дереву синтаксически — она так и должна ходить, это структурная свёртка, и другой ей быть нельзя. Причина в том, что факт из объявления НЕ СТРОИЛ НИКТО: ни обязательства, ни принцип индукции, ни ядро.
Теперь строит факты по типу (flang/self/proof-kernel.flang): по одному факту «имя не меньше 0» на каждое имя, объявленное как неотрицательное. Имена берутся из ИМЕНА_НАТ типизатора, а не переписаны рядом: второй такой список разошёлся бы молча, и 自然数 перестал бы быть фактом для ядра, оставшись им для типизатора.
Это не аксиома. неотрицательное — не имя, а отрезок [0, 2⁵³−1] (flang/self/types.flang): подпись принимает первое: неотрицательное и есть написанный автором факт «первое не меньше 0», и типизатор проверяет его на каждом вызове. Ядро этот факт не выдумывает и не выводит — оно читает его там, где он написан. Правило «шаг без названного факта отвергается» не нарушено: факт назван подписью функции. Список АКСИОМЫ по-прежнему пуст.
Фактом становится только неотрицательное. целое — это [−2⁵³+1, 2⁵³−1], число — вообще всё, включая NaN и −∞; ни то, ни другое о знаке не говорит ничего. Обе подмены стоят изъятиями в проверке ядра.
Мест, куда факт приезжает, два, и оба — уже стоявшие:
- Посылка индукции. У каждой посылки принципа появились
declarations: параметры функции (кроме переменной индукции — в заключении она уже заменена конструктором) и поля-коэффициенты варианта под именами образца. Тип поля — такой же факт, как тип параметра:вариант «Слой» содержит вес: неотрицательноеговорит «вес не меньше 0» ровно так же, какпринимает вес: неотрицательное. Отсюда шаг вида(«Вес» от н) плюс вессводится, а до этой работы отвергался. - Постусловие БЕЗ теоремы. Оно больше не пропускается молча: ядро делает одну подстановку (тело функции на место
результат— ту же самую, какойflang/self/proof-initial.flangстроит заключение посылки) и один вызовсвести. Не свелось — вердикта НЕТ: ни отказа, ни диагностики, и утверждение остаётся сеткой примеров, как стояло. Отказать здесь было бы неправдой: постусловие без теоремы — не ошибка.
Почему без девятого слова поверхности. Написать при таком утверждении теорему значило бы пересказать подпись: индукции не по чему (у неотрицательное нет объявленных вариантов, принцип читать не с чего), а цепочка состояла бы из единственного шага «это следует из объявления». Слово по типу пришлось бы завести в таблице ключевых слов самоприменённого лексера — то есть увеличить ровно тот долг, в который слой доказательства и упёрся. Долг не вырос ни на слово.
Ведомость назвала это своим словом. Вердикт несёт поле by: "declaration", свод считает proved-declaration рядом с proved-induction. Сказать «терм принят» здесь было бы неправдой: терма нет вовсе, и шагов в нём ноль.
И ещё одно слово, разведённое с этим позже и по улике. Путь «постусловие без теоремы» закрывает не только цели, где факт дал объявленный тип. Утверждение «результат не меньше 0» у функции над строка, чьё тело — восемь ветвей выбора с литералами, сводится ЦЕЛИКОМ БЕЗ ЕДИНОГО ОБЪЯВЛЕНИЯ: работают литерал и выбор. Пока ведомость печатала на такое «доказано по объявленным типам аргументов», она называла источником факт, которым никто не пользовался, — та же неправда в ту же сторону, что «доказано» вместо «сетки», только мельче. Улика появилась на первом же корпусном утверждении: двух из трёх.
Различает их не строка отчёта, а ЯДРО, и различает ИЗЪЯТИЕМ: flang/self/proofterm.flang зовёт свести второй раз, без объявлений, и кладёт ответ в поле byType. Свелось и без них — значит объявленный тип не понадобился, и ведомость говорит «доказано сведением цели с телом функции: объявленные типы аргументов не понадобились». Перебора при этом ноль: второй вызов такой же прямой, как первый. Свод печатает два числа порознь — «без теоремы» и «из них факт дал объявленный тип» (proved-declaration и proved-by-type), — потому что вопросы у них разные, а свернув их в одно, свод потерял бы ровно то различие, ради которого заведён.
Ловушка, на которой это ломается, проверена, а не обещана. Правило верно для суммы и для выбора и ЛОЖНО для разности и произведения. Выдумывать проверку не пришлось: в том же файле корпуса строкой ниже стоит «Разность пары» — те же два неотрицательное, первое минус второе, и пример 2 минус 3 = −1 написан там автором языка. Негативных проверок пять: минус, умножить и деление при тех же неотрицательное; число и целое вместо неотрицательное; подделка в индукции (минус при объявленном неотрицательное поле).
3б-кватер. Вторая граница того же объявления: потолок 2⁵³−1
неотрицательное — не признак знака, а отрезок [0, 2⁵³−1], то есть ДВА факта на каждое имя. Раздел выше отдал ядру первый. Второй тогда отдан не был, и довод был записан здесь же прямым текстом:
Потолок ядру здесь не нужен и потому не берётся: правило неотрицательности спрашивает только про знак, а факт, которым никто не пользуется, — это доверие, за которое никто не отвечает.
Довод был верен ровно до того, как у потолка появился отвечающий. Он появился: граница входа сверяет --args с объявленным типом ДО вычисления (checkArguments, ветка work/entry-types-113). Прежде --args '{"н": 1e300}' втекало в программу молча, и это ломало доказательство ТОТАЛЬНОЙ функции: неотрицательное несёт её завершение потолком (ниже 2⁵³−1 н минус 1 точно меньше н, поэтому сторож не печатается вовсе), а 1e300 минус 1 равно 1e300, и цепочка вечна. Теперь значение вне неотрицательное до тела не доходит — и потолок стал таким же читаемым фактом, как дно. Верить дну и не верить потолку было бы верой наполовину: держатся оба на одной подписи.
Улика ДО, снятая прогоном. Утверждение «результат не больше 9007199254740991» о «Разности пары» (docs/examples/measure/natural.flang) — ПРАВДА, и написана она в корпусе прозой рукой автора языка строкой над функцией: «А вот РАЗНОСТЬ натуральных остаётся в точной сетке всегда — она может стать отрицательной, но точность не теряет». Типы утверждение проходило (диагностик 0), а ядро на нём молчало: вердиктов 0, свести отвечал «у цели этого случая нет вида, к которому у ядра есть правило».
Теперь строит ограниченность по типу (flang/self/proof-kernel.flang): по одному факту «имя не больше 2⁵³−1» на каждое имя, объявленное неотрицательное. Число берётся из верх точной сетки (flang/self/types.flang) — из того самого точного потолка, которым построен сам тип NAT, а не переписывается здесь литералом: переписанное число разошлось бы молча, и ядро отмерило бы отрезок не той длины.
Граница из цели сравнивается, а не подразумевается. Факт даёт имя ≤ 2⁵³−1 и ничего сильнее: цели имя не больше 10 он не закрывает, натуральное бывает и одиннадцатью. Имя попадает в известные, только если спрошенная граница не ниже потолка типа, и граница на единицу ниже потолка отвергается — это проверка, а не рассуждение.
ГЛАВНАЯ ЛОВУШКА, ИЗ-ЗА КОТОРОЙ ЭТУ ПОЛОВИНУ И ОТКЛАДЫВАЛИ: суммы в правиле нет. Потолок — факт про АРГУМЕНТ, а не про результат. Правило дна сумму берёт, и это истинно; правило потолка сумму не берёт, и это тоже истинно: сумма двух неотрицательное выходит за потолок законно. Написано это не здесь, а в самом корпусе (docs/examples/measure/natural.flang, раздел 5):
Сложение двух натуральных натуральным быть не обязано: 2⁵³−1 плюс 2⁵³−1 выходит за точную сетку double… Анализ это видит и РАСШИРЯЕТ тип до
число.
Перенеси кто-нибудь случай сложения из первого правила в третье — ядро начало бы доказывать ЛОЖЬ о «Сумме пары», стоящей в том же файле строкой выше. Поэтому а плюс б при двух неотрицательное с целью «не больше 2⁵³−1» обязано быть отвергнуто, и это отдельная проверка, названная в тесте ГЛАВНОЙ ЛОВУШКОЙ. Изъятие проведено: допиши случай сложения — краснеют две проверки поимённо.
Вычитание берётся — и на двух посылках сразу. а минус б ≤ П выводится из а ≤ П и б ≥ 0, причём вторая посылка спрашивается у правила дна тем же списком известного. Доказательство о IEEE-754: при б ≥ 0 точное а − б не больше а, округление к ближайшему монотонно, а представимо — значит fl(а − б) ≤ а ≤ П. NaN появиться негде: конечное П исключает +∞ у а, б ≥ 0 исключает NaN и −∞ у б, а ∞ − ∞ — единственный способ получить NaN вычитанием. Ровно поэтому граница обязана быть конечным литералом: с границей +∞ правило стало бы ложным.
Убери одну посылку — правило ложно, и обе половины изъяты проверками: вычитаемое число (тогда (2⁵³−1) минус (−5) за потолком), уменьшаемое число (выводить нечего вовсе). Отдельно стоит тонкий случай: оба целое. У целое ТОТ ЖЕ потолок, что у неотрицательное, — и этого мало, потому что правилу нужно ещё дно вычитаемого, а его целое не обещает. Ядро молчит, и утверждение при двух целое действительно ложно.
Умножения и деления здесь нет, и теперь это расходится с правилом дна намеренно: (2⁵³−1) умножить на 2 за потолком, а частное упирается в деление на ноль. Расхождение списков стало двусторонним — правило дна берёт и сумму, и произведение ограниченных, правило потолка не берёт ни того, ни другого, — и свести их в один список значило бы соврать в обе стороны сразу.
Проверок у правила тринадцать, и негативных среди них больше, чем положительных. Враждебная выборка — двойник той, что стоит у правила дна: 17 значений (включая оба нуля, обе бесконечности, NaN, оба края точной сетки) на 4 границах; правило не нарушено ни разу, а «сумма ограниченных ограничена» нарушается — и это тоже утверждается, иначе список случаев выглядел бы закрытым по лени, а не по доказательству.
3б-квинтэ. Четвёртый источник фактов: РЕЗУЛЬТАТ ВСТРОЕННОЙ ФОРМЫ
Здесь стоял разрыв, стоивший ядру трети корпусного заказа, и назвать его можно одной строкой: у ядра не было ни одного факта о том, что возвращает встроенная форма. Оно не знало даже, что длина списка не меньше нуля, — а это правда по построению, на любом входе, включая пустой список.
Улика измерена, а не вспомнена. Обход flang/core и examples (136 функций, у которых «результат не меньше 0» истинно на всех примерах автора) разложил причины отказа по числу, и первые две — свёртка и встроенная длина. На всём корпусе (3838 функций) ядро брало цель у 17.
Факт не выдуман — он уже посчитан типизатором. длина объявлена неотрицательное (flang/self/types.flang: «счёт элементов конечного дерева неотрицателен и целый по построению»), код символа — отрезком [0, 0x10FFFF] («Unicode кончается на 0x10FFFF, и это граница не IEEE-754, а стандарта»). Ядро читает ту же таблицу (ТИП_РЕЗУЛЬТАТА_ФОРМЫ), а не переписывает числа рядом: разойдись два списка — ядро сочло бы неотрицательным то, что типизатор таковым не считает. Довод тот же, по которому имена неотрицательное берутся у типизатора, а не в ядре.
Форм в таблице две из девятнадцати, и остальные семнадцать перебраны поимённо. Перебор стоит исполняемым тестом: список форм берётся у рантайма (BUILTIN_NAMES), и появись двадцатая — она приедет в проверку сама.
| форма | почему факта нет |
|---|---|
символ, символы, подстрока, соединить, разделить, к строке, голова, хвост, элемент, добавить, содержит, начинается с, пусто, к числу или беда | результат не число (или произвольное значение элемента) |
к числу | число любого знака: к числу «-5» даёт −5 |
остаток от | самая похожая на правду. Остаток наследует знак ДЕЛИМОГО: остаток от −5 и 3 равен −2. Сверх того остаток от 5 и 0 даёт NaN, а NaN не больше и не меньше нуля. Два независимых способа получить ложь; оба стоят контрпримером в тесте |
процентов от | (процент / 100) умножить на значение — знак любой (50 процентов от −8 равно −4) |
Потолка у формы ядро НЕ читает, хотя он в той же таблице. длина объявлена неотрицательное, то есть с потолком 2⁵³−1, и правило 3 могло бы взять его тем же чтением. Оно не берёт, и это решение: потолок длины держится не языком, а памятью машины (довод типизатора: «списка длиннее 2⁵³−1 в этой вселенной не существует»), а ставить размер оперативной памяти в цепочку доверия нельзя. Дно же держится построением счёта и верно на любой машине.
3б-секстэ. СВЁРТКА: индукция началом и шагом
Самый дорогой заказ ядра по числу утверждений (пункт 6 раздела «Что дальше»), и причина, по которой он стоял, была названа прямо: терм непрозрачен, а принцип индукции к нему не цепляется — заключение посылки читается с ВЕТВЕЙ разбор, а у тела-свёртки их ноль.
Индукция у свёртки всё-таки есть; брать её надо не с ветвей, а с самой свёртки: у неё есть НАЧАЛО и ШАГ, и это ровно две посылки индукции по списку.
свёртка над С начиная с Н шагом (а, э) → Т неотрицательна, если
Н неотрицательно ← база
Т неотрицательно при допущении «а не меньше 0» ← шаг
Это вывод, а не постулат. Значение свёртки есть aₙ, где a₀ — значение Н, а a_{к+1} — значение Т в окружении, где а равно a_к, а э равно (к+1)-му элементу (flang/self/interpret.flang). Список конечен — обход снимается снимком до первого шага, — значит индукция по его длине законна ровно постольку, поскольку законно допущение о хвосте у принципа индукции: довод один и тот же, «часть значения меньше значения». База: a₀ ≥ 0 по первой посылке. Шаг: пусть a_к ≥ 0; тогда в окружении шага истинно «а не меньше 0», прочие известные факты истинны тоже (их свободных имён свёртка не связывает), и по второй посылке Т не меньше нуля. Пустой список — случай к = 0: результат есть само начало, и о нём говорит база. Список АКСИОМЫ не пополнился ни на строку.
Способов получить отсюда ложь три, и у каждого стоит проверка с контрпримером:
| способ | контрпример | что стережёт |
|---|---|---|
| шаг прибавляет ЭЛЕМЕНТ | свёртка [−1] начиная с 0 шагом акк плюс эл даёт −1 | об элементе не известно ничего, и вывод обязан быть верен при ЛЮБОМ элементе |
| начало отрицательно | свёртка [] начиная с −1 даёт −1 | на пустом списке результат есть само начало |
| факт о ПЕРЕСВЯЗАННОМ имени | свёртка [−1] … как акк и н → н при н: неотрицательное даёт −1 | известное о внешнем имени вычёркивается, если свёртка связала то же имя |
| одно имя на накопитель и элемент | — | вычислитель связывает элемент ПОСЛЕ накопителя, тело говорит об элементе; ядро отказывает, а не гадает |
Третья строка — та самая половина инварианта «правило не спускается под связыватель», которая раньше держалась запретом. Запрет снят (в свёртка правило теперь заходит), и держится инвариант вычёркиванием, что строже: у него есть контрпример, а у запрета его не было.
Цена обоих правил измерена изъятием, а не обещана. Охват по всему корпусу (3838 функций, цель «результат не меньше 0»):
| ядро | охват |
|---|---|
| оба правила | 43 |
| без правила формы | 30 |
| без правила свёртки | 27 |
| без обоих (как было) | 17 |
Числа не складываются (17 + 13 + 10 = 40, а не 43), и это само по себе улика: трём функциям нужны оба правила сразу — у них свёртка складывает длины.
Из библиотеки этим закрылись два утверждения, стоявшие сеткой: «счёт вхождений неотрицателен» (lists.flang) и «счёт по условию неотрицателен» (higher-order.flang). Ведомость по корпусу: доказано ядром было 29, стало 31; сетка была 19, стала 17, всего высказано 49 — то же самое.
3б-септимэ. Четвёртый ход: ЗАМКНУТАЯ ЦЕЛЬ ВЫЧИСЛЯЕТСЯ
Улика, с которой началось, — две функции без параметров, отличающиеся ровно видом замкнутой цели:
тотальная функция «Число»
возвращает число
обеспечивает «ровно сто двадцать восемь» результат равен 128
128
→ доказано сведением цели с телом функции: правило «тождество после переписки
допущением» … теоремы при нём нет и не нужно
тотальная функция «Перечень»
возвращает строка
обеспечивает «открыт оградой» результат начинается с "|"
"|неотрицательное|натуральное|"
→ объявлено, не доказано: ни теоремы, ни примеров
Замкнуты обе. Разница была не в замкнутости, а в том, что вид первой цели попал в список трёх правил, а вид второй — нет. Считать вторую ничто не мешало: у выражения без свободных имён значение ровно одно.
РЕШАЮЩИХ ПРАВИЛ ПО-ПРЕЖНЕМУ ТРИ, и это проверяется списком, а не прозой (проверка ядра: «решающих правил по-прежнему ТРИ»). Вычисление — не правило вывода:
| правило (три) | вычисление (четвёртый ход) | |
|---|---|---|
| чем решает | по ПОСТРОЕНИЮ выражения | счётом |
| при свободных именах | работает (в этом весь смысл: неотрицательное-имя — факт обо ВСЕХ его значениях) | не берётся вовсе |
| при каком виде цели | при трёх названных | при любом |
| что добавляет к доверенному | ничего | ничего: тем же вычислением уже решает по примеру |
Последняя строка — главная. Ядро уже решает вычислением, и решает им единственное правило, которое закрывает случай целиком: по примеру прогоняет пример тем же интерпретатором, которым язык считает всё остальное. Довод там записан с самого начала: «закрытый терм — это значение, и спорить о нём не о чем, надо посчитать». Здесь тот же довод и тот же интерпретатор; ново одно — место, откуда ход позван. АКСИОМЫ не пополнились ни на строку.
Три условия, и ни одно не украшение:
- ЗАМКНУТОСТЬ ПРОВЕРЯЕТСЯ, А НЕ ПРЕДПОЛАГАЕТСЯ. Считает её та же
свободныеИмена, какой её считает посылка индукции дляпо примеру: два ответа на вопрос «какие имена здесь свободны» разошлись бы молча. Осталось хоть одно имя — значений столько же, сколько подстановок, и вычисление одного из них было бы ровно тем сортом лжи, который в этом ядре уже находили. - ПРЕДЕЛ НАЗВАН (
ПРЕДЕЛ_ВЫЧИСЛЕНИЯ— витки и глубина) и при исчерпании отказывает словами: «не досчитали» и «неправда» — разные ответы автору. Состоятельности число не касается: остановиться раньше значит доказать МЕНЬШЕ, а не доказать ложное. - ПРИНИМАЕТСЯ РОВНО
да.нет— это ЛОЖНОЕ утверждение, и отказ говорит именно это; не признак — цель не утверждение вовсе; отказ вычисления — цель значения не имеет.
Вычислитель приезжает в свести ДАННЫМИ, пятым доводом, — тем же приёмом, каким приезжают определения для развёртки. Не дали вычислителя — хода нет, и ответ свести тот же, знак в знак, каким был до этой работы. На этом стоит сверка с эталоном на flang: она зовёт свести двумя доводами, сличает отказ побуквенно и осталась зелёной целиком.
ДОЛГ НАЗВАН ПРЯМО: у четвёртого хода эталона на flang НЕТ. flang/self/proof-kernel.flang повторяет три правила и четыре переписки, и сверка с ним честна ровно потому, что ход спрашивается только при переданном вычислителе, а сверка его не передаёт. Написать эталон этому ходу значит написать на flang вызов интерпретатора flang — то есть не переписать правило, а дать эталону вычислитель; работа это отдельная и к сведению отношения не имеющая. Пока её нет, справедливо ровно следующее: правила ядра сверены с эталоном целиком, вычисление — нет, и ни одно «доказано» на нём не стоит молча: ведомость называет его своим словом («доказано вычислением замкнутой цели»), а не общим.
ЗАПИСЬ ВЫШЕ УСТАРЕЛА В ОДНОМ СЛОВЕ, И ЦЕНА ТЕПЕРЬ ИЗМЕРЕНА. Вычислитель у эталона ЕСТЬ — flang/self/interpret.flang, сверенный на 2 318 примерах из 2 318. Мешает не его отсутствие, а его цена, и вот она числами:
| что мерено | число |
|---|---|
| зовов вычислителя свидетелем на всём корпусе | 43 |
| из них с ЗАМКНУТОЙ целью, то есть со счётом | 12 |
| файлов, где эти 12 стоят | 4, по три на файл |
| витков на саму цель | 10 |
| подготовка программы эталоном: 1 функция | 29 мс |
28 функций (stdlib/lists.flang) | 673 мс |
552 функции (self/carriers.flang) | 5 679 мс |
1074 функции (self/sets.flang) | предел 40 000 000 витков исчерпан за 10,9 с БЕЗ ответа |
1201 функция (self/monad.flang) | то же за 11,5 с |
2028 функций (self/bootstrap/compiler.flang) | то же за 19,4 с |
Читается это однозначно: ход упирается не в правило доказательства, а в чужой слой. Сама цель ничего не стоит — это арифметика на литералах плюс один вызов без аргументов; всё съедает Подготовить, обходящее дерево программы целиком, а под доказательством стоят программы в 1000–2000 функций. Сверка гоняет корпус 18 раз (прогон плюс 17 изъятий), значит 12 счётов превращаются в 216.
Обходов ровно два, и оба отвергнуты по существу. Считать замкнутые цели В СВЕРКЕ и подавать эталону данными (как подаются прогоны) — значит вернуть «доказано» в зависимость от JavaScript, ради ухода от которой работа и затевалась. Готовить не всю программу, а только достижимое с цели — значит дать вычислителю не ту программу, что у свидетеля, и разойтись молча на первой же зависимости от «имён записей» или «тегов».
Снимется позиция удешевлением подготовки в self/interpret.flang — это работа по слою вычисления, а не по ядру доказательства.
ЦЕНА ПОДГОТОВКИ СНЯТА (17 августа), И ПРЕПЯТСТВИЕ ТЕПЕРЬ ДРУГОЕ
Таблица выше остаётся как запись «что было». Работа по её последней строке сделана: Подготовить больше не обходит дерево программы. Мест было два, и оба названы числом, а не «медленно».
Первое — теги, 77 % подготовки. Список случаев диспетчера применить собирался ЗАРАНЕЕ обходом всего дерева: 66 829 узлов и 12 320 793 витка на self/carriers.flang, 184 витка на узел. У свидетеля тот же список считается ЛЕНИВО (flang/self/interpret.flang) — это и было единственное расхождение замысла между двумя подготовками. Теперь и эталон считает его лениво: в «Программе» лежит не список, а программа и имена её функций. Спрашивает о нём ровно одно место — исполнение узла применить; во всём корпусе сверки функцию берут значением 1 файл из 125, а применений исполняется 90 на 2609 примеров.
Второе — повтор имени, 16 % подготовки, и он квадратичен. Проверка «функция объявлена дважды» искала имя своей рекурсией по уже собранным описаниям, то есть N·(N+1)/2 зовов «Найти описание». Прогон подтвердил формулу знак в знак: 152 628 зовов при 552 функциях (2 588 052 витка), 577 275 при 1074, 2 057 406 при 2028 — 35 млн витков при пределе сверки в 40 млн. Рядом с описаниями теперь едут их имена, и вопрос задаётся встроенной формой содержит — один виток на функцию вместо N.
| подготовка | было | стало |
|---|---|---|
552 функции (self/carriers.flang) | 15 926 783 витка / 3 753 мс | 1 027 320 / 365 мс |
1074 функции (self/sets.flang) | предел исчерпан за 10,8 с | 2 139 799 / 792 мс |
1201 функция (self/monad.flang) | предел исчерпан за 12,6 с | 2 399 637 / 918 мс |
2028 функций (self/bootstrap/compiler.flang) | предел исчерпан за 19,2 с | 3 875 684 / 1 478 мс |
Рост стал линейным — около 1 900 витков на функцию. Изъятие проведено: правка снята, и число вернулось знак в знак (15 926 783 на carriers.flang), а три большие программы снова упёрлись в предел.
Ход прогнан целиком, а не оценён. Обёртка-цель дописана к программе тем же способом, каким её дописывает вычислениемЗамкнутой, программа подготовлена эталоном, обёртка позвана: замкнутая цель считается на ТРЁХ из четырёх названных программ — sets.flang за 875 мс, monad.flang за 890 мс, bootstrap/compiler.flang за 1 395 мс. Сама цель стоит 6 витков машины.
Четвёртая, repl/repl.flang, не проходит, и препятствие у неё В ДРУГОМ ФАЙЛЕ. Её подготовка стоит 136 899 309 витков, из них 132 270 681 (96,6 %) — Поставить сторожей из self/defunc.flang: связанная программа тянет объявленные меры, и постановка сторожей переписывает дерево из 410 800 узлов по 333 витка на узел. У прочих трёх поручений нет вовсе, и проход отдаёт программу как есть за 196 витков. Внутри прохода измерено и второе, помельче: «Дописать функцию» пересобирает список имён на каждую функцию — 11 807 369 витков, тот же квадрат N·(N+1)/2. Арифметика говорит прямо: снятие квадрата плюс пропуск скаляров (66 % узлов) дают вместе около 55 млн — полумерами предел в 40 млн не пройти, нужна переработка самого прохода, а он сверяется побайтово восемью целями печати. Это отдельная работа, и она названа здесь числом, чтобы её не пришлось открывать заново.
ХОД ПЕРЕНЕСЁН ЦЕЛИКОМ 18 АВГУСТА, И ЗАЗОР ЯДРА ПУСТ.
Сверка эталона со свидетелем: 291 программа из 291 побайтово, расхождений 0, названный зазор 0. Список ЗАЗОР в сверке ядра с двойником стал ПУСТЫМ СПИСКОМ — он по-прежнему сличается deepEqual-ом, то есть краснеет теперь на любом расхождении вообще.
Долг был переписан числом трижды, и каждый раз прежняя запись оказывалась ложной. Записан он был как «у эталона вычислителя нет» — вычислитель есть. Потом как ЦЕНА подготовки — цена была снята 17 августа. Потом выяснилось, что цена вообще не при чём: в зазоре остались программы в 27, 1 и 12 функций, подготовка на них стоит 40, 2 и 10 мс, а на замкнутой цели эталон-вычислитель тратит 10–17 витков. Мешала ФОРМА ПЕРЕДАЧИ ХОДА, и вот чем она снята.
Переезд слоя — то, чего в оценке не было ни у кого. «Свободные имена» с помощниками (15 функций) стояли в self/proof-initial.flang, а тот ИМПОРТИРУЕТ self/proof-kernel.flang, где живёт Свести. Слой, обязанный решать «свободно ли здесь хоть одно имя», лежал НИЖЕ слоя, умеющего ответить, и ход был заперт направлением ребра, а не ценой счёта. Функции переехали вниз; текст перенесён знак в знак, места вызова не тронуты — оба читателя видят их через использует.
Явный довод вместо замыкания. У свидетеля ход отдаётся замыканием: вычислениемЗамкнутой захватывает программу и имя обёртки и уезжает пятым доводом в свести. В языке замыкания нет и не будет — на нём ломается доказательство завершаемости, ради которого язык существует. Поэтому программа проведена ЯВНО через 13 функций цепочки сведения, седьмым доводом нового входа Свести счётом. Пять прежних входов отдают Нет программы, и это вычислитель = null свидетеля: хода нет, ответ знак в знак прежний — на этом стоит сверка ядра с двойником, зовущая свести без шестого довода.
Мест вызова оказалось ДВА, а не восемь, и это сверено со свидетелем, а не выбрано: вычислитель у него уезжает ровно в двух местах — сведение цели с телом функции (flang/self/proofterm.flang:672) и посылка, закрытая самим ядром (:966). Прочие шесть зовут свести без хода, и правильно зовут. Изъятие «понадобился ли объявленный тип» цель, закрытую вычислением, не трогает: вычисление в объявления не смотрит вовсе, ответ изъятия известен заранее.
Цена, уплаченная за ход. Эталон считает три бывшие красными программы за 561, 359 и 180 мс против 82, 62 и 116. Доказанных ядром в ведомости стало 132 из 153 — прежние 132 из 151, то есть ход не отнял ни одного.
АКСИОМЫ ПРИ ЭТОМ ОСТАЛИСЬ ПУСТЫ, и это проверяется отдельным тестом. Довод — тот же, по которому вычислением закрывает случай по примеру: закрытый терм это значение, и спорить о нём не о чем, надо посчитать. Доверенных сущностей не прибавилось.
Цель отдаётся вычислителю КАК ПРИШЛА, а не нормальной формой. Интерпретатор сам разворачивает пусть, разбор и вызов — правилами языка, а не ядра, — и ход не зависит от нормализации вовсе, в том числе от её ограничителя на захват имени.
Что это дало числом (измерено изъятием теоремы у каждого утверждения, а не оценено): на корпусе ветки work/lemmy ядро закрывало САМО 20 утверждений из 110, стало — 70. Пятьдесят утверждений, которым автор написал и теорему, и пример, перестали требовать и того, и другого; шесть оставшихся требуют индукции, и правильно требуют. На корпусе main число не изменилось (20): целей такого вида в нём просто не было.
И тот же ход закрывает базу индукции. Перечисление из трёх вариантов доказывается теперь ОДНИМ примером вместо трёх: посылки с замкнутым заключением ядро закрывает само и говорит, чем. Дописывание примера на каждый вариант и составляло половину объёма файлов в flang/proof/examples/.
3в. Почему правило сведения — не аксиома
Список АКСИОМЫ по-прежнему пуст, и это не формальность.
Аксиома — утверждение, принятое БЕЗ доказательства; ложная аксиома отравляет всё, что из неё выведено. Правило «сумма неотрицательных неотрицательна» — теорема о IEEE-754, и вот её доказательство целиком: посылка а не меньше 0 истинна только для не-NaN значений из [0, +∞] (NaN не сравним ни с чем, и на нём посылка ложна); сумма двух значений из [0, +∞] под округлением к ближайшему снова лежит в [0, +∞], потому что переполнение даёт +∞, а не отрицательное. Знака терять негде.
Ровно поэтому в списке нет «произведение неотрицательных неотрицательно»: 0 умножить на +∞ даёт NaN, и это утверждение ЛОЖНО. Нет вычитания и деления — по той же причине. И нет х плюс 1 больше х — это и есть тот сорт аксиомы, из-за которого список аксиом пуст.
А умножение при этом в правиле есть, и разница между ним и ложным утверждением выше — ровно одна посылка. Ложным «произведение неотрицательных неотрицательно» делает единственный вход — бесконечность, — и посылка не меньше 0 её не исключает (+∞ не меньше 0 истинно). Исключает её ВТОРАЯ граница той же подписи: неотрицательное есть отрезок [0, 2⁵³−1], и потолок ядро читает наравне с дном. Отсюда теорема, которую ядро и применяет:
если
аибкаждый не меньше 0 и не больше конечного, тоа умножить на бне меньше 0.
Доказательство целиком: посылки оставляют от каждого сомножителя КОНЕЧНОЕ неотрицательное; NaN получить негде (0 × ∞ исключено конечностью, NaN в аргументе — посылкой не меньше 0); точное произведение неотрицательно, а округление к ближайшему монотонно и ноль оставляет нулём, поэтому fl(а·б) лежит в [0, +∞] — переполнение даёт +∞, а не отрицательное.
Посылка требуется от каждого сомножителя и проверяется отдельной свёрткой вОтрезке, список случаев которой короче списка неотрицательности: ни сложения, ни умножения в нём нет, потому что обе операции выводят из отрезка переполнением, а +∞ — ровно то значение, ради исключения которого посылка и заведена. Поэтому н умножить на м при двух неотрицательное доказуемо, а (н плюс м) умножить на к — нет. Изъятие проведено трижды: снят случай mul, ослаблена посылка до одной лишь неотрицательности, снята конечность границы — каждый раз краснеют названные проверки.
Третий источник факта — объявленный тип — тоже не аксиома. Имя, объявленное неотрицательное или вес, неотрицательно, и вывод состоит из двух проверяемых шагов: (1) оба типа объявлены отрезками с дном 0, и записано это в одном месте (flang/self/types.flang, откуда имена импортированы в flang/self/proof-kernel.flang, а не переписаны рядом); (2) значение на такой позиции ПРОВЕРЕНО типизатором при каждом вызове — годится пропускает туда только то, чей отрезок вложен в объявленный, а NaN не вложен ни в какой отрезок. Опора названа вслух, как она названа у точного шага в flang/self/totality.flang: снимите проверку типов — и это доказательство станет неверным. Это объявленная зависимость одного анализа от другого, а не скрытое допущение.
В списке нет целое: у него дно −(2^53−1), и «не меньше 0» про него ложно. Нет число: у него дна нет вовсе. Список закрыт двумя именами ровно потому, что дно есть ровно у двух типов языка.
Сколько это даёт на настоящем корпусе — считается одной командой, node flang/scripts/weight-gain.mjs: из 139 функций корпуса, которые берут число и возвращают число, ядро выводит «результат не меньше 0» у 4 при нынешних объявлениях (три из них — программа paths/shortest-path.flang, уже написанная на весе; до заведения типа доказуемая была ровно одна) и у 25, если числовые параметры объявить вес. Прирост 21, потерь 0.
Двадцать из двадцати одной — рукописные минимумы и максимумы («Минимум двух», «Максимум двух», «Ограничить» из stdlib/numbers.flang, «Меньшее из» и «Большее из» из self/types.flang, «Минимум трёх» из rosetta/levenshtein-distance.flang и так далее). Это не совпадение: минимум и максимум и есть те две операции, которые образуют на [0, +∞] настоящий коммутативный моноид, — корпус писал их руками задолго до того, как у языка появился тип, на котором о них есть что доказать.
Разница между аксиомой и правилом проверяется, а не обещается: проверка ядра перебирает правило по враждебной выборке (оба нуля, NaN, обе бесконечности, MAX_VALUE, MIN_VALUE, EPSILON) и отдельно показывает, что изъятое из списка действительно ложно.
ЧЕТВЁРТОЕ ПРАВИЛО — «цель есть допущение» (Γ, A ⊢ A) — тоже не аксиома, и довод у него другой, чем у трёх первых. Три первых заведены каждое под свой ВИД цели (не меньше 0, не больше конечного, равно), и у каждого поэтому свой разбор про IEEE-754. Это работает на цели ЛЮБОГО вида, и вида не спрашивает вовсе — отсюда и подозрение, которое надо отвести первым.
Подозрение в рефлексивности, и оно законное: Е не больше Е в IEEE-754 НЕ теорема, на не число сравнение ложно, и рефлексивность порядка держится только посылкой «не NaN». Похоже, что «цель есть допущение» — та же рефлексивность, только про утверждения, и что у неё обязан быть свой NaN. Его нет, и разница точная: рефлексивность порядка говорит нечто ОБ ОПЕРАЦИИ не больше, применённой к двум копиям значения, и потому зависит от того, как эта операция ведёт себя на краю носителя. Правило естьДопущением не применяет НИ ОДНОЙ операции языка. Оно говорит, что одно и то же выражение имеет одно и то же значение, — и не число в это утверждение попасть неоткуда: сравнения в нём нет вовсе, а одинаковы сличает ДЕРЕВЬЯ.
Доказательство после этого в две строки. Допущение A значит здесь ровно одно: «выражение A при данном входе вычисляется в да» — признаком его объявил не автор, а типизатор. Цель A значит то же самое о том же выражении. Значит при истинном допущении цель истинна — и не «следует», а СОВПАДАЕТ: это одно утверждение, записанное дважды. Теоремой это делает то, что совпадение ПРОВЕРЯЕТСЯ узел в узел, а не объявляется.
Посылок у доказательства две, и обе уже несут вес в ядре — то есть новых допущений правило не заводит, и обе названы вслух, как названа опора точного шага в flang/self/totality.flang:
| посылка | чем держится сегодня | что было бы без неё |
|---|---|---|
| ОДНО ОКРУЖЕНИЕ: имена цели и имена допущения означают одно | тем же, чем правило тождества (оно переписывает стороны ЦЕЛИ равенствами ИЗ ДОПУЩЕНИЙ) и разбор по условию («язык чист»); ломается ровно захватом имени, а захват стережётся в flang/self/proof-initial.flang и в развёртке (ОГРАНИЧИТЕЛЬ 4), обе проверки написаны по уликам и стоят подделками в проверке непротиворечивости | текст цели совпал бы с текстом допущения о ДРУГОМ значении |
| ДОПУЩЕНИЕ ДЕЙСТВИТЕЛЬНО ДОПУЩЕНО | посылку индукции ядро СТРОИТ само (по предположению), предусловие берётся с объявления требует и оплачивается на КАЖДОМ месте вызова (снятьПредусловия); ссылку на доказываемое сейчас постусловие отдельно отвергает по свойству | ровно та аксиома, из-за которой список аксиом пуст: правилу, которому «допущение» подаёт сам автор, доказуемо что угодно, начиная с нет |
Подделки на правило лежат в проверке предусловий и метят в первую посылку: у обеих предикат тот же, что у допущения (не пусто …), и разойдись правило с «дословно» хоть на шаг — читай оно «про ту же проверку» вместо «то же выражение», — обе прошли бы. Обе отвергнуты кодом FLANG_PRECONDITION_CALL, и вторую отвергает само правило: отказ называет его поимённо.
Механизм подсмотрен у datatype-пакета Isabelle (BSD): там принцип индукции тоже порождается из универсального свойства, а не постулируется. Подсмотрен механизм, не код. Ядро Isabelle целиком не переносится, и причина содержательная помимо объёма: Pure — логический каркас, HOL определён поверх него объектной логикой, а наши программы — не термы HOL. Перенеся Pure, мы получили бы проверяльщик термов HOL и задачу погружения семантики flang в HOL — ту же самую, что «выгрузка во внешний прувер». Мы идём другим путём: своё ядро над нашей категорной основой. (Отдельно юридическое: Coq под LGPL-2.1, производная работа от него столкнулась бы с лицензионным контуром проекта — язык под BSD-2. Isabelle под BSD, Lean 4 под Apache-2.0.)
Четыре правила, и каждое решает вопрос без поиска:
- по примеру — утверждение ЗАКРЫТО и вычисляется интерпретатором в
да. Закрытый терм это значение, спорить не о чем, надо посчитать. Считает тот же интерпретатор, которым язык считает всё остальное: второго вычислителя нет. «Закрыто» проверяется в обоих местах, где правило работает: внутри индукции — у случая не связано ни одного имени, вне индукции — у функции нет ни одного параметра. Второе условие появилось позже первого и по улике (см. ниже). - по предположению — утверждение это цель, в которой переменная индукции заменена частью значения, связанной образцом. Ядро его СТРОИТ, а не сличает — единственный способ не дать назвать предположением что угодно.
- по свойству — ссылка на другое постусловие. Ссылка на доказываемое сейчас постусловие отвергается: это круг, и
по предположениюсуществует именно затем, чтобы круга не было. Постусловие с ограничением (для всех н таких что P обеспечивает …) даёт факт о вызове только тогда, когдаPс доводами этого вызова на месте параметров выведено из того, что известно на месте вызова: изтребуетвызывающей, объявленных типов и уже добытых фактов. Не выведено — шагпо свойствуотвергается, а факт о вызове не берётся и без ссылки, когда цель сводится по постусловиям вызванных. - по закону — факт от закона моноида, монады или изоморфизма. Такой терм принимается, но ослабляет вердикт до сетки: законы посчитаны на конечной сетке автора, а доказательство не может быть сильнее того, на что опирается. Ровно так теряется вся разница между доказанным и посчитанным, если промолчать об этом один раз.
Свод вердиктов — одно правило на всё ядро: слабейшее побеждает, обратного хода нет.
3в-бис. ЧЕТВЁРТОЕ правило: порядок с границей-ТЕРМОМ (18 августа 2026)
Правил сведения стало четыре, аксиом по-прежнему ноль.
⚠ И ЭТО ЧИСЛО УСТАРЕЛО: правил восемь. Ниже сказано «случаев пять, список закрыт» — случаев шесть, шестой это У24 (прибавление к ГРАНИЦЕ неотрицательного конечного литерала). См. раздел «Восемь правил ядра и 28 умений, которых в описании не было».
Три прежних решали Е не больше Г только при конечном литерале справа. Корпус же сравнивает с выражением: («Длина» от элементы), (длина элементы), («Длина» от первые). Семь утверждений получали один и тот же отказ — «у цели этого случая нет вида, к которому у ядра есть правило».
Почему это теорема, а не аксиома. Первое, что надо сказать вслух: в IEEE-754 Е не больше Е НЕ теорема. На не число это ЛОЖЬ — NaN не сравним ни с чем, включая себя. Значит рефлексивность требует посылки «граница не есть NaN», и правило, написанное без неё, — ровно та дыра, ради которой список аксиом держат пустым. Посылку эту даёт правило 1 и только оно: терм, о котором выведено не меньше 0, не есть NaN, потому что NaN не меньше 0 ложно. Нового источника фактов правило не открывает ни одного.
Случаев на день написания раздела было пять (сегодня шесть — см. У24), каждый — отдельная теорема о IEEE-754:
- допущение — факт
Е не больше Гуже назван посылкой; - тождество —
Е ≡ Гсинтаксически ИГ не меньше 0выведено; - выбор —
если у то а иначе бподГ, когда подГобе ветви; - литерал под дном границы —
Л ≤ дно(Г), где дно собирается из неотрицательности (0), из литерала (он сам) и из суммы дон (тем же сложением, каким её посчитает машина); - прибавление ОДНОГО И ТОГО ЖЕ КОНЕЧНОГО литерала к обеим сторонам известного порядка:
А ≤ БдаётА плюс К ≤ Б плюс Кмонотонностью округления; конечностьКобязательна — приК = +∞из−∞ ≤ 5вышло быNaN ≤ +∞.
Чего нет и почему. Вычитания: а минус б ≤ Г при а ≤ Г и б ≥ 0 верно только при КОНЕЧНОМ Г, а о конечности терма-границы не обещает никто (вес есть [0, +∞]). Правило 3 берёт вычитание ровно потому, что там граница — конечный литерал. Умножения нет по причине правила 3. Транзитивности через чужой терм нет: искать середину значило бы завести перебор, а поиска у ядра нет.
При правиле живёт одна переписка: длина(звено{г, х}) в 1 плюс длина(х), а длина(пусто) в 0. Это не аксиома — конструкторы встроенного списка ядро читает у типизатора, а значение длина на них считает интерпретатор. Живёт она ПРИ правиле, а не в нормализовать: нужна она одному правилу и ровно затем, чтобы посылка индукции (о ХВОСТЕ) и цель (о ЗВЕНЕ) встретились на одном числе.
Подделок четыре, и каждая бьёт в свою посылку (проверка ядра): Е не больше Е при границе х делить на х; 1 не больше (длина элементы) — ложь на пустом списке; прибавление РАЗНЫХ литералов; та же ложь программой целиком, с кодом FLANG_PROOF_INDUCTION_STEP. При каждой стоит положительная пара: отвергать можно и всё подряд.
Сколько это дало НА САМОМ ДЕЛЕ: одно из семи, и это надо сказать прямо
Ожидание было «семь разом»: семь утверждений отказывались ОДНИМ отказом, и отсюда читалось, что причина у них одна. Прогон это опроверг. Одинаков был ПЕРВЫЙ отказ, а не единственный. Свод: доказано 120 → 121, сетка 26 → 25.
Закрылось одно — позиция не выходит за список (corpus-hof.flang). Остальные шесть упираются в препятствия, к виду цели отношения не имеющие, и назвать их надо поимённо, чтобы следующий не мерил то же дважды:
| утверждение | что мешает СВЕРХ вида цели |
|---|---|
индекс не выходит за список | граница — вызов ПОЛЬЗОВАТЕЛЬСКОЙ «Длина», а не встроенной формы; о знаке чужого вызова ядро при сведении не знает ничего. Нужен пятый источник фактов — постусловие вызываемого (ход седьмой) внутри правил |
отбрасывание не удлиняет, взятие не удлиняет | посылка индукции говорит о Отбросить(хвост, сколько), а тело зовёт Отбросить(хвост, сколько минус 1). Нужна ИНСТАНЦИАЦИЯ посылки по свободным именам, а ядро сличает допущения синтаксически |
сжатие не длиннее первого списка | в цели остаётся разбор по ВТОРОМУ списку; хода «разбор цели по разбору» у ядра нет (пятый ход делит только по если) |
уникальные не удлиняют, счёт по условию не больше длины | тело — свёртка, и инвариант накопителя ЗДЕСЬ НЕ СОХРАНЯЕТСЯ: из акк ≤ длина(элементы) не следует акк плюс 1 ≤ длина(элементы). Это не пробел ядра, а свойство утверждения: оно связывает результат со СПИСКОМ, а инвариантом доказывается только то, что говорит о результате (см. 3д, ход шестой) |
Три из четырёх строк — работа, которой правило сведения не является вовсе: источник фактов, инстанциация посылки, шестой ход деления цели. Записано это здесь затем, чтобы «семь» больше не обещалось числом, которого замер не даёт.
3г. Второй принцип: индукция по ОТРЕЗКУ неотрицательное
До этой работы принцип порождался ровно с одного места — с объявления суммы. У неотрицательное объявления нет: это встроенное имя, конструкторов у него ноль, функтор читать не с чего. Отсюда следовал соблазн, и назвать его надо прямо: завести аксиому индукции по числу. Она не заведена, и список АКСИОМЫ остался пуст.
Откуда взят принцип. неотрицательное — не «неотрицательное число», а ОТРЕЗОК целых [0, 2⁵³−1] (flang/self/types.flang). Отрезок КОНЕЧЕН, а у конечного упорядоченного множества всякая строго убывающая цепочка конечна — не по соглашению, а потому что иначе в нём было бы бесконечно много различных значений. Принцип «доказали дно, доказали переход с н минус 1 на н — доказали обо всём отрезке» есть теорема о конечном отрезке, и обе посылки её ядро проверяет, а не принимает на слово.
Где здесь ровно та ловушка, из-за которой аксиом ноль. «х минус 1 меньше х» в IEEE-754 ЛОЖНО при большом |х|: при х = 2⁵³+4 округление к ближайшему возвращает то же самое х. Индукция по числу без верхней границы — эта самая ошибка. Держат её ОБЕ границы объявленного типа, и каждая закрывает свою половину:
| граница | что даёт | что было бы без неё |
|---|---|---|
| потолок 2⁵³−1 | СТРОГОСТЬ спуска: внутри отрезка соседние целые представимы точно, поэтому н минус 1 вычисляется точно и строго меньше н | н минус 1 равно н — цепочка не убывает, а допущение говорит о том же значении, что и заключение |
| дно 0 | ОСТАНОВ: условие дна читается закрытым списком форм, у каждой верх дна не отрицателен, а спуск ровно на единицу | цепочка проскакивает дно и уходит за отрезок, где допущение говорит о значении, которого у типа не бывает |
Поэтому индукция по число и по целое отвергается, и отказ называет причину числом: у число в носителе живёт +∞, у которого х минус 1 равно самому х (спуска нет вовсе), у целое нет дна. Проверено не рассуждением: 2**53+4-1 === 2**53+4 и Infinity-1 === Infinity стоят равенствами в проверке ядра.
Что читается, и ни одного поиска. Пять мест, все написаны автором:
- тип переменной индукции — в подписи функции;
- мера — словом
убывает. По сумме его писать не надо (убывание следует из построения значения), по числу не следует ничего, и что убывает, обязан назвать автор. Слово в языке уже стояло — девятого слова поверхности принцип по отрезку не потребовал ни одного; - условие дна — первым
еслипо переменной индукции. Форм пять, список закрыт:п не больше К,п меньше К,п равно 0,п больше К,п не меньше К. У двух форм (п меньше Кип не меньше К)Кобязан быть целым: верх дна у них считается какК минус 1, и при дробномКэтот верх ВРЁТ. Это не осторожность, а починка найденной дыры:если н меньше 2.5прин: неотрицательноеотправляет в базу ин = 2, а посылка базы получала факт «н не больше 1.5» — и ядро печатало «доказано индукцией» об утверждении, которое рантайм тут же отвергалFLANG_PROPERTYнан = 2. Теперь такая запись отвергается кодомFLANG_PROOF_INDUCTION_BRANCH, и подделка стоит проверкой; - какая ветвь база — ФОРМОЙ условия, а не догадкой о содержимом ветви;
- на чём стоит рекурсивный вызов — аргументом вызова.
Факты посылок читаются с того же если, и их ДВА, а не один. База получает п не больше верх, шаг — п больше верх: в ветвь спуска вычисление попадает ровно тогда, когда условие дна ложно. Это не вывод и не догадка, а вторая половина той же прочитанной формы, и для всех пяти записей она пишется одинаково — вот построчная сверка:
| форма условия | верх | факт базы | факт шага, и почему он истинен |
|---|---|---|---|
п не больше К | К | п ≤ К | условие ложно ⟹ п > К |
п меньше К (К целое) | К−1 | п ≤ К−1 | ложно ⟹ п ≥ К, а К > К−1 |
п равно 0 | 0 | п ≤ 0 | ложно ⟹ п ≠ 0, и п из неотрицательное не меньше 0 |
п больше К | К | п ≤ К | база — ветвь иначе, спуск — то: п > К |
п не меньше К (К целое) | К−1 | п ≤ К−1 | спуск — то: п ≥ К > К−1 |
Факт шага — единственный источник строгой положительности во всём ядре (раздел 3б), и без него не замыкается ни одно рекурсивное умножение.
Что ядро проверяет, а не принимает на слово: СПУСК. Аргумент рекурсивного вызова обязан быть н минус 1 и ничем иным. н плюс 1, н минус 0, вызов на самом н, н минус 2 — отвергаются кодом FLANG_PROOF_INDUCTION_DESCENT. Код свой, а не общий с шагом, и это не мелочь: «шаг не сведён к допущению» и «спуск, на котором стоит допущение, никуда не спускается» — разные находки и разные починки; вторая означает, что доказательства не было вовсе.
Образцы у отрезка — те же, что у разбор, и своих не заведено. Дно — это случай 0 (литерал, попадающий в дно), спуск — случай любое. Имени у любое быть не должно: связывать нечего, значение одно и уже названо переменной индукции.
Живой пример: flang/proof/examples/segment.flang — две функции над неотрицательное, обе доказаны. У первой базу закрывает само ядро и случая при ней нет; у второй дно написано случай 0 и закрыто примером, и ядро сверяет, что значение примера в дно попадает.
Сколько корпуса этим взялось — измерено, а не предположено, и мерка та же. Здесь стояло: «у 31 функции корпуса есть параметр неотрицательное; у 17 принцип по отрезку ЧИТАЕТСЯ целиком; обе посылки не сводятся сегодня ни у одной», и дальше — что шесть функций возьмутся, «как только правило умножения появится». Правило появилось, и половина предсказания оказалась верной, а половина нет. Обе половины измерены одним обходом (flang/self/proof-initial.flang и свести на каждой посылке), и числа такие:
| до этой работы | после | |
|---|---|---|
функций корпуса с параметром неотрицательное | 33 | 34 (+corpus-factorial.flang) |
| принцип по отрезку читается целиком | 19 | 20 |
| обе посылки сводятся | 2 (обе — фикстуры segment.flang) | 9 |
Девять — это две прежние фикстуры, новый corpus-factorial.flang и ШЕСТЬ настоящих функций корпуса: «Факториал» на всех четырёх поверхностях, «Факториал» из docs/examples/measure/natural.flang и «Факториал» из flang/stdlib/numbers.flang.
Но написать утверждение и теорему можно только у ТРЁХ из этих шести, и препятствия к ядру доказательства отношения не имеют ни одно:
factorial.eo.flangиfactorial.zh.flang— восьми слов доказательства у эсперантской и китайской поверхностей НЕТ ВОВСЕ (flang/self/lexer.flang, «ВОСЕМЬ СЛОВ ДОКАЗАТЕЛЬСТВА без эсперанто и без китайского»). Утверждение о тех файлах не высказать — не «не доказать», а именно не высказать;stdlib/numbers.flang«Факториал» — параметр названчисло, а это КЛЮЧЕВОЕ СЛОВО (тип). Постусловие пишется (для всех число обеспечивает …), адано число: неотрицательноеииндукция по числоразбору не поддаются вовсе.
Всё это стоит исполняемыми проверками в проверке утверждений набора, а не словами: заведись эсперантское слово или переименуйся параметр — тесты покраснеют и потребуют переписать этот абзац.
А «Степень» из предсказания надо вычеркнуть, и не потому, что ядро не тянет. Утверждение «результат не меньше 0» о ней ЛОЖНО: основание объявлено число, и «Степень» от (0 минус 2) и 1 равно −2. Доказывать там нечего.
Остальное упирается по-прежнему НЕ в индукцию: у «Фибоначчи шагом» и «Ступени шагом» спуск сводится допущением, а дно ЛОЖНО — накопитель объявлен число, и «результат не меньше 0» о них неправда («Фибоначчи шагом» от 0 и (−7) и 9 равно −7).
3г-бис. Носитель принципа читается с ОБЪЯВЛЕНИЯ типа, а не выбирается перебором из трёх (6203, ADR-0026 шаг 1)
До 10 сентября 2026 носитель индукции выбирался закрытым перебором трёх функций — «Случаями по имени», «Отрезком по имени», «Свёрткой по имени» — и тот же список из трёх стоял второй раз в независимой проверяющей программе. Любой тип вне трёх давал молчаливую «сетку», а не отказ. Теперь (fdc14f0be, в семени с 0ce948bfd) ядро сначала читает ОБЪЯВЛЕНИЕ типа довода индукции — «Принцип типа по объявлению» (flang/self/proofterm.flang): объявленная сумма и встроенные список/строка дают algebra (и fold над свёрткой по тому же доводу), псевдоним тип «Т» это Х разворачивается по цепочке, имя из «Названо натуральным» даёт segment, всё прочее — «Отказ без принципа» с причиной, названной поимённо (число: в носителе живёт +∞; параметр типа; функция: объявления с конструкторами нет). Запись печатает под принципом строку объявление … (proof-record.flang, «Слить строки записи» … " объявление "): у суммы — варианты с числом рекурсивных частей, у отрезка — дно и потолок, у псевдонима — цепочку; проверяющая программа читает то же объявление с исходника сама (checker.c, объявление_типа) и сличает строку знак в знак — Ш0/Ш2 задачи 6203. Пример из корпуса, снятый семенем 0ce948bfd 11 сентября 2026 (flang/proof/examples/corpus-length.flang):
принцип тип «список» по «элементы» носитель algebra база 1 шаг 1
объявление встроенный «список» варианты «пусто» 0 «голова и хвост» 1
Подделка «носитель segment над доводом типа число» отвергается обеими программами с одной причиной («принципа индукции по нему нет»), а не проходит кодом 3, как до Ш0.
3д. Три ФОРМЫ ТЕЛА, за которые принцип цепляется (16 августа 2026)
Улика ДО, числом. Замер цены доказательства (docs/benchmark-proof-cost-2.md) назвал узкое место со знаменателем: заключение посылки индукции ядро строило ТОЛЬКО с ветвей разбор по переменной индукции. Таких тел в flang/stdlib 58 из 208. Остальные 150 написаны свёрткой (45), условием (37), арифметикой (21), вызовом (18), встроенной формой (9), пусть (8), отображением и отбором (8), построением (3), применением (1). То есть 72 % библиотеки написано формами, к которым принцип не цеплялся ничем, и отказ на всех был один и тот же: FLANG_PROOF_INDUCTION_BRANCH. На двадцати функциях замера — 7 отказов из 18.
Ходов прибавилось три. Решающих правил по-прежнему ТРИ, аксиом ноль.
Ход пятый: РАЗБОР ЦЕЛИ ПО УСЛОВИЮ
Цель, у которой на верхнем уровне стоит если, делится надвое подстановкой ЗНАЧЕНИЯ условия, и обе половины сводятся теми же тремя правилами.
Законно это потому, что условие У — выражение типа признак (проверил типизатор), а у признака значений ровно два. Значит цель совпадает либо с собой, где У заменено на да, либо с собой, где У заменено на нет. Доказав обе, доказали цель.
Это НЕ то, что уже измерено нулём. docs/zettel/reading-if-conditions-closed-zero-goals.md записывает: чтение условий если КАК ФАКТОВ закрывает ноль целей, и ветвь иначе читать нельзя вовсе — на не число ложны обе стороны сравнения, и из «не (х меньше у)» не следует «х не меньше у». Здесь НИ ОДИН факт из условия не выводится: подставляется значение. Ловушка не число про перевод отрицания сравнения в другое сравнение, а здесь отрицания нет.
Границы, и каждая в коде:
- не под связывателем. Условие берётся только с того
если, до которого обход дошёл, ни разу не войдя внутрьразбор,свёртка,отобразить/отфильтровать,пусть. Под связывателем у выражения нет одного значения. Тем же обходом ограничена и ЗАМЕНА. - предел.
ПРЕДЕЛ_ВЕТВЛЕНИЯ= 4. Каждое деление удваивает число целей; самое глубокое вложение условий в корпусе — три. Достижение предела говорит о себе словами. - половина закрыта двумя способами и только ими: сведена теми же правилами или стала литералом
да. Ставшая литераломнет— ЛОЖНА, и отказ говорит это.
Ход шестой: ИНДУКЦИЯ ПО СВЁРТКЕ
Тело свёртка <переменная индукции> начиная с И как а и э → Т даёт две посылки: начало (заключение P(И)) и виток (заключение P(Т) при допущении P(а)).
Сказать надо прямо: это НЕ индукция по списку. Свёртка в flang ЛЕВАЯ (flang/self/interpret.flang), и её второе определяющее уравнение
свёртка (г : х) начиная с И как а и э → Т = свёртка х начиная с Т[а:=И, э:=г] …
меняет НАЧАЛО, а не только список. Допущение по списку говорило бы о свёртке хвоста при том же начале, заключение — о свёртке хвоста при другом начале, и свести одно к другому нечем. Это свойство левой свёртки, а не пробел ядра: доказательство по списку потребовало бы обобщения утверждения по накопителю, то есть ПОИСКА ИНВАРИАНТА, которого у ядра нет и не будет.
Читается вместо этого принцип по числу ВИТКОВ: витков ровно столько, сколько ячеек в списке (длина снимается один раз, до первого витка), утверждение, истинное о начале и переживающее виток, истинно о результате. Тот же ход уже жил ВНУТРИ правила неотрицательности (неотрицательнаяСвёртка); эта работа вынула его в построение посылки, чтобы им пользовались все три правила.
Граница, которая здесь стояла, СНЯТА 22 августа 2026. Прежде инвариантом накопителя доказывалось только то, что говорит о РЕЗУЛЬТАТЕ, а утверждение, связывающее результат со СПИСКОМ («размер равен длине входа»), не бралось: при пустом списке оно о начале ложно. Теперь P говорит о ДВУХ вещах — о накопителе и о ПРОЙДЕННОМ куске списка:
P(И, пусто) ∧ (для любых а, п и э: P(а, п) ⟹ P(Т, п ++ [э])) ⟹ P(свёртка L начиная с И …, L)
Это теорема, а не аксиома, и выводится она из тех же двух определяющих уравнений. Из них следует третье — то, на котором стоит виток:
свёртка (п ++ [э]) начиная с И … = Т[а := свёртка п начиная с И …, э := э]
Вывод — индукция по «п» при допущении, годном для ЛЮБОГО начала: на пустом «п» обе стороны сводятся к Т[а:=И, э:=э] первым уравнением; на «п = г : х» левая сторона вторым уравнением переходит в свёртку «х ++ [э]» при начале Т[а:=И, э:=г], там применяется допущение, и вторым же уравнением собирается правая. Сам принцип после этого — индукция по «п» при Q(п) := P(свёртка п начиная с И …, п), а подстановка п := L даёт вывод.
Читается это в программе так: цель, где стоял список свёртки, получает на его место пусто в основании и добавить э к пройдено в витке — в конец, а не в начало, потому что свёртка левая и на витке номер k пройдены первые k элементов. Обе подстановки (результат и список) кладутся ОДНОВРЕМЕННО, поэтому список, упомянутый в начале свёртки или в теле витка, остаётся собой, и заключение честно остаётся со свободным именем. Имя пройденного куска берётся свободным от всего занятого — цели, подписи, начала, тела и списка свёртки.
Подделка на все четыре новых права — flang/test/fixtures/poddelka-svyortka-prefiks.flang (переворот, выдающий себя за тождество; шаг на два вместо одного; ложное основание; чужой список); честная половина — flang/test/fixtures/svyortka-prefiks-chestnaya.flang.
Три отказа, и каждый закрывает способ получить ложь: имена накопителя и элемента обязаны быть разными; ни одно из них не имеет права быть свободным в самой цели; объявленные типы этих имён в посылки не едут.
Ход седьмой: СЛИЧЕНИЕ ПО ВЫЗОВУ
по свойству «имя» существовало на поверхности с самого начала и отказывало всегда: «свести „результат“ шага с результатом другой функции ядру пока нечем… дождитесь сличения по вызову». Дождались.
Факт — не вера: вычислитель проверяет постусловие ПОСЛЕ КАЖДОГО возврата (flang/self/interpret.flang), и нарушенное постусловие прекращает вычисление отказом FLANG_PROPERTY. Значит у вызова, у которого есть значение, постусловие вызываемого на этом значении истинно. Ядро читает написанное слово — ровно так же, как читает тотальная, разворачивая вызов. Оговорка та же, что у правила неотрицательности: у вызова без значения утверждение не истинно и не ложно, оно не высказано.
ОТ ЧЕГО ЭТОТ ХОД ЗАВИСИТ, СКАЗАНО ЗАРАНЕЕ. Он опирается на то, что постусловие ЛИБО доказано ядром, ЛИБО проверяется на каждом возврате. Первый пункт списка недостающего (docs/lemmas-report.md) предлагает «не проверять доказанное постусловие в рантайме» — и это ход СОВМЕСТИМЫЙ: снимается проверка ровно с того, что доказано, а недоказанное остаётся под проверкой. А вот снять проверку с НЕДОКАЗАННОГО постусловия нельзя, не сломав это правило, и здесь это записано затем, чтобы поломка не прошла молча.
Подстановка ЧИТАЕТСЯ: в заключении ищутся узлы вызов «Г» от …, и на каждом постусловие «Г» инстанцируется двумя подстановками — параметры на аргументы ЭТОГО вызова, результат на сам вызов. Кандидатов столько, сколько вызовов. Нет ни одного вызова «Г» — отказ говорит именно это. Круг (ссылка на доказываемое сейчас постусловие) остаётся запрещён. Выписанное автором утверждение шага обязано быть заключением этого места, иначе посылка считалась бы доказанной по тому, что к ней не относится.
Долг закрыт: все три хода перенесены в эталон (17 августа 2026)
Записан долг был так: «у ходов пятого и шестого эталона на flang НЕТ». Теперь есть у всех трёх, и это измерено побайтовой сверкой, а не объявлено.
Ход разбора по условию включается ШЕСТЫМ доводом свести (витков), и ноль по умолчанию по-прежнему значит «хода нет». Приём не изменился — изменилось то, что эталон теперь зовёт свести с ПРЕДЕЛ_ВЕТВЛЕНИЯ во всех местах, где его называет свидетель («Свести витками» в self/proof-kernel.flang), а три старых входа Свести остались с нулём: на них стоит сверка ядра с двойником, которая зовёт свести свидетеля без шестого довода.
| ход | эталон | чем закрыт |
|---|---|---|
| пятый — разбор цели по условию | есть | «Условие цели», «Подставить условие», «По условию», «Свести половину» в self/proof-kernel.flang |
| шестой — индукция по свёртке | есть | «Свёртка по переменной» и «Принцип по свёртке» в self/proof-initial.flang, третье порождение при ОДНОЙ свёртке принципа |
| седьмой — сличение по вызову | есть | «По свойству» и «Инстанция постусловия» в self/proofterm.flang |
Справедливо теперь ровно следующее: три правила и три хода сверены с эталоном побайтово на всём корпусе; не сверено вычисление замкнутой цели (четвёртый ход) — и не потому, что правило не перенесено, а потому, что цена подготовки у вычислителя-эталона линейна по размеру программы. Ни одно «доказано» на непереданном ходе не стоит молча: ведомость называет каждый ход своим словом, а зазор назван поимённо в сверке ядра с двойником.
Числом: было 248 из 255 побайтово при семи названных расхождениях, стало 258 из 263 при пяти, и все пять — четвёртый ход. body-forms.flang, ради которого три формы тела и писались, у обоих ядер сходится знак в знак.
Что это дало числом
| было | стало | |
|---|---|---|
| постусловий закрыто на двадцати функциях замера | 6 из 21 | 9 из 22 |
| из них СОДЕРЖАТЕЛЬНЫХ (говорят о поведении) | 2 | 4 |
| высказано по корпусу | 138 | 142 |
| доказано ядром по корпусу | 111 | 115 |
| из них индукцией | 21 | 23 |
| отвергнуто / нарушено | 0 / 0 | 0 / 0 |
| аксиом | 0 | 0 |
Подделки и изъятия на каждый ход — проверка форм доказательства, 15 проверок. Пример корпуса — flang/proof/examples/body-forms.flang.
Граница честности: что ядро доказывает целиком
Рекурсивный случай доказывается. Это и есть та работа, ради которой писался слой начальной алгебры, и потолок языка ею снят: утверждение о функции над бесконечным типом теперь может стоять в ведомости словом «доказано», а не «сеткой N значений автора». Живой пример: flang/proof/examples/stack.flang — два утверждения о «Стопке», у которой значений бесконечно много, доказанные двумя посылками каждое.
Что ядро доказывает целиком:
- Утверждения о конечной сумме, закрытые в каждом случае — перечисления, у которых все конструкторы без полей. База закрывается вычислением примера. Пример:
flang/proof/examples/traffic-light.flang. - Утверждения о рекурсивной сумме, у которых заключение шага сводится к допущению одним из двух правил (раздел 3б). Примеры:
flang/proof/examples/stack.flangи четыреcorpus-*.flang— настоящие функции корпуса«Высота»,«Размер дерева»,«Глубина»и«Глубина дерева», перенесённые дословно. - Утверждения, у которых заключение шага говорит о результате ДРУГОЙ функции — если её определение разворачивается (раздел 3б-бис). Примеры: «высота копии равна высоте оригинала» в
stack.flang(развёртка по конструктору) иcorpus-tree-depth.flang—«Глубина дерева»изflang/stdlib/tree.flang, чей шаг упирался в«Глубже»(плоское определение). - Утверждения о функции, рекурсивной ПО ЧИСЛУ — индукцией по отрезку
неотрицательное(раздел 3г). Примеры:flang/proof/examples/segment.flangиcorpus-factorial.flang— настоящий «Факториал» изdocs/examples/rosetta/factorial.flang, перенесённый дословно вместе со всеми четырьмя примерами. Спуск при этом проверяется, а не принимается: подделка, у которой шаг не убывает, — отвергается.
4-бис. Утверждения, у которых шаг стоит на УМНОЖЕНИИ рекурсивного вызова. Это то место, где встречаются оба принципа этого раздела, и закрылось оно двумя чтениями сразу: посылка шага читает свой если (п больше верх), а у умножения появилась вторая посылка (один сомножитель в (0, конечное]). Ни одного нового слова поверхности и ни одной аксиомы это не потребовало.
- Посылку, которая сводится БЕЗ ЕДИНОГО ДОПУЩЕНИЯ ИНДУКЦИИ — и закрывает её само ядро, без случая при ней. Так закрыт долг «базу нечем закрыть, кроме примера»:
«Глубина»изdocs/examples/leetcode/104-…доказывается теперь БЕЗ единой дописанной строки, а её копия вcorpus-depth.flangперестала нести пример, которого в корпусе нет. - Утверждение, цель которого ЗАМКНУТА, — каким бы ни был её вид (раздел 3б-квинт). Свободных имён нет — значение одно, и вычисление отвечает про него целиком. Сюда попадает всё, о чём у ядра правила нет и не предвидится:
начинается с,входит в,длина, свёртка по таблице. На корпусеwork/lemmyэто 50 утверждений, у которых теорема и пример перестали быть нужны.
- Утверждения о функциях над ВСТРОЕННЫМ списком — тем же принципом и теми же двумя правилами (раздел 3а-бис). Три доказаны на настоящих функциях библиотеки:
«длина неотрицательна»и«индекс неотрицателен»оflang/stdlib/lists.flang,«позиция неотрицательна»оflang/stdlib/higher-order.flang. Два последних — на функциях ДВУХ параметров, а третье — на функции, у которой первый параметр сам функция: она проезжает посылку коэффициентом и индукции не мешает.
Что ядро НЕ доказывает и отвергает вслух:
- Случай, в заключении которого осталось свободное имя, поданный примером. Пример — одно значение, а у незакрытого заключения их столько же, сколько подстановок. Проверяется заключение, а не образец, и это исправление, а не формулировка: проверка образца ошибалась в обе стороны сразу. Слишком слабо — у функции двух параметров образец
случай пустоне связывает ничего, а в заключении оставался второй параметр (случай пусто то нпри цели «результат не меньше 0» давало ЛОЖНОЕн не меньше 0и закрывалось примером с н = 5). Слишком сильно — образец со связанными именами при теле-литерале даёт закрытое заключение, и отказывать ему значило требовать доказательства у вычисленного. Обе стороны стоят негативными тестами в проверке ядра, и ложность первой измерена рантаймом нан = −1, а не рассуждением. - Утверждение о функции С ПАРАМЕТРАМИ, поданное примером ВНЕ индукции. То же правило и та же причина, но проверки здесь не было: она стояла только в ветви случая, а доказательство без
индукция послучая не имеет вовсе. Найдено программой, а не чтением: постусловие«Длина» от результат равен 3у«Соединить списки»— утверждение обо ВСЕХ парах списков, ложное почти на всех, — получало вердикт «доказано ядром: терм принят, 1 шаг», потому что пример «Два непустых» и правда даёт три. Примером закрывается только утверждение с ОДНИМ значением, то есть функция без параметров; изъятие и обратная половина (функция без параметров принимается по-прежнему) стоят в проверке ядра. - Цель, в которую подстановка ЗАХВАТИЛА БЫ имя. Подстановка
результат := <тело>вычёркивала связанное имя КЛЮЧА и не смотрела на имена ЗНАЧЕНИЯ, а докстрингподставитьутверждал прямым текстом «захвата здесь быть не может». Цель сосвёртка … как акк и х → результатполучала на месторезультаттело, свободное вх, ихуезжал под чужой связыватель: снаружи параметр функции, внутри элемент свёртки. Цель(свёртка [1] начиная с 0 как акк и х → результат) равно (свёртка [1] начиная с 0 как акк и х → х)означает «результат равен 1»; после захвата обе стороны становятся одним термом, и правило тождества сходится знак в знак. Ловилось на обоих путях сразу — и без теоремы («доказано сведением цели с телом функции»), и С НЕЙ («доказано ИНДУКЦИЕЙ по «Дерево» … обо ВСЕХ входах типа «Дерево»»), причём во втором случае захват ещё и ПРЯТАЛ свободное имя от проверкипосылка.free: связанное свободным не считается, значит базу пускало правилопо примеру, а пример автора честно проходил — он написан на значении, где утверждение случайно верно. Шаг при этом сводился по-настоящему: ложь пролезла не через дырявое правило сведения, а через дырявую подстановку ПЕРЕД ним. Починка — ОТКАЗ, а не переименование, по той же причине, что у развёртки: выдуманное имя не сличить с допущением.подставитьспрашиваетзахватываети возвращаетnull, а ядро читаетnullкак «заключения нет». Отказ называет причину отдельно от «нет ветви разбора»: обе кончались однимconclusion === null, и по первому сообщению автор пошёл бы править тело функции, которое в порядке. Обе улики и обе границы — в проверке непротиворечивости. Эталон починен вместе со свидетелем, и сверка перестала быть слепой к этому. Молчала она не по доброте: у неё было 90 входов подстановки и ни одного с захватом — затенение КЛЮЧА стерегли, имена ЗНАЧЕНИЯ нет. Входов стало 176, по одному на каждый из четырёх связывателей плюс обратная половина (связыватель назвал ДРУГОЕ имя — подстановка обязана пройти). Изъятие показывает цену: снимите захват у эталона — сверка краснеет 12 расхождениями из 176, а до новых входов показывала 0 из 90. - Спуск по числу, который не убывает — код
FLANG_PROOF_INDUCTION_DESCENT(раздел 3г), и это главная негативная проверка второго принципа. - Шаг, не сводящийся тремя правилами. Отказ называет правило, которое не прошло, и код
FLANG_PROOF_INDUCTION_STEP. - Посылку, у которой связанное имя непрозрачного поля ПОПАЛО В ЗАКЛЮЧЕНИЕ — например
«Есть» содержит значение: «А», когда тело ветви это значение возвращает. Допущения там нет (поле не типа самой суммы), а примером случай не закрыть (имя свободно). Это ближайшая оставшаяся граница, и закроет её не индукция, а упрощатель (раздел «Что дальше»). - ~~Индукцию по строке~~ — ЭТО УСТАРЕЛО, и держать его здесь опаснее, чем пропуск: по такой строке человек не станет писать доказательство, которое на самом деле проходит. Индукция по строке ЕСТЬ и работает: улика У8 (
«Первый знак»вflang/proof/map/abilities.flang) получает вердиктдоказано индукцией по «строка»: база 1 случай, шаг при допущении на частях (1 случай), правила сведения: ограниченность точным потолком по построению. То же измерено вdocs/proof-goal-map.md, клетки К3 и К4. Закрывает она цели о МЕРЕ строки; про содержимое строки молчит по той же причине, что и всё остальное — у цели о содержимом нет вида. - ~~Ссылку
по свойствуна постусловие другой функции~~ — ЭТО УСТАРЕЛО, и файл сам себе противоречил: раздел «Ход седьмой: СЛИЧЕНИЕ ПО ВЫЗОВУ» выше говорит «отказывало всегда… Дождались», а этот перечень продолжал числитьпо свойствусреди неработающего. Опаснее пропуска: по такой строкепо свойствуне станет писать никто. Прогон настоящего файла дерева:
`` bootstrap/flang check --proof flang/proof/examples/body-forms.flang постусловие «и это неотрицательно» функции «Выше на два» — доказано: терм принят ядром, 1 шаг — утверждение обо ВСЕХ входах утверждений 4: доказано 4 … код возврата 0 ``
Теорема при «Выше на два» состоит ровно из одного шага по свойству «высота неотрицательна». Перемерено ОТДЕЛЬНОЙ пробой, чтобы вердикт не зависел от соседей по файлу, — две функции, вторая зовёт первую:
```flang тотальная функция «Мера» принимает элементы: список число обеспечивает «мера неотрицательна» результат не меньше 0 длина элементы
тотальная функция «Мера с запасом» принимает элементы: список число обеспечивает «запас неотрицателен» результат не меньше 0 2 плюс («Мера» от элементы)
теорема «запас неотрицателен» дано элементы: список число утверждаем результат не меньше 0 по свойству «мера неотрицательна» следовательно доказано ```
`` постусловие «запас неотрицателен» функции «Мера с запасом» — доказано: терм принят ядром, 1 шаг — утверждение обо ВСЕХ входах утверждений 2: доказано 2 … (шагов в термах 1) код возврата 0 ``
Что по свойству ссылается на ОБЕЩАНИЕ, а развёртка читает ТЕЛО, — верно и осталось верным: вещи разные, и опереться на обещание можно там, где тело разворачивать нельзя (рекурсия, неотрицательное, чужой модуль). Круг (ссылка на доказываемое сейчас постусловие) по-прежнему запрещён.
- Развёртку вызова на неизвестном значении —
«Копия» от хпри связанномх,«Фибоначчи шагом»с рекурсией по числу. Ограничители названы в разделе 3б-бис, и оба случая стоят проверками.
Подделка отвергается, и это проверено. Неверное утверждение, поданное с тем же самым доказательством индукцией, которое проходит на верном, ядро отвергает — причём база подделки честно проходит, и ложь видна только в шаге. Это главная негативная проверка файла в проверке ядра; без неё слой был бы машинкой, штампующей слово «доказано».
Аксиом у ядра по-прежнему ноль (АКСИОМЫ пуст). Почему правила сведения ими не являются — раздел 3в.
Восемь правил ядра и 28 умений, которых в описании не было
Улика. 22 августа 2026 написали второй, независимый проверяльщик доказательств — flang/proof/small-reducer.flang, — и написали его ПО ОПИСАНИЮ, а не по коду ядра. Сличили на 752 утверждениях в 257 файлах:
расхождений 31
из них ошибки второго проверяльщика 3
из них СПОСОБНОСТИ ЯДРА, КОТОРЫХ НЕТ
В ОПИСАНИИ 28
Ни одного места, где ядро было бы неправо. Все двадцать восемь расширений проверены и оказались истинными: это долг описания, а не дыра в ядре.
Доверие к слою сужено до восьми НАЗВАННЫХ правил. Но если описание правил беднее самих правил, человек, читающий описание, не знает, чему на самом деле верит. И практически: всякий, кто напишет третий проверяльщик или возьмётся ядро сузить, упрётся в те же двадцать восемь мест и потратит на них день. Один уже потратил.
Правил восемь, а не три, не четыре и не шесть
Число правил в этом файле росло по ходу работ, и старые разделы ниже называют его так, как он был на день их написания (три — раздел 3б, четыре — раздел 3в-бис). Сегодня правил восемь, и называет их само ядро, в текстах отказов:
| # | правило | вид цели |
|---|---|---|
| 1 | неотрицательность по построению | Е не меньше 0 |
| 2 | ограниченность точным потолком по построению | Е не больше <конечный литерал> |
| 3 | тождество после переписки допущением | А равно Б |
| 4 | порядок по построению | Е не больше <терм> |
| 5 | вхождение по построению | Л содержит Э |
| 6 | порядок соседних по построению | Л не убывает |
| 7 | цель есть допущение | вида цели не спрашивает |
| 8 | несовместимые допущения | вида цели не спрашивает |
Первые шесть — про ФОРМУ цели, и ровно их ядро перечисляет в отказе «у цели этого случая нет вида, к которому у ядра есть правило». Седьмое и восьмое вида цели не спрашивают: седьмое сличает цель с допущением знак в знак, восьмое закрывает случай, недостижимый по несовместимости допущений.
Список умений лежит в ОДНОМ месте, и на него ссылаются обе стороны
flang/proof/tables/abilities.tsv — 28 строк, по одной на умение. В строке: номер, правило ядра, умение словами, якорь (дословный кусок текста отказа в flang/self/proof-kernel.flang) и имя функции-улики.
Улики — flang/proof/map/abilities.flang, по функции на умение. Вердикт берётся не из этой таблицы, а прогоном:
bootstrap/flang check --proof flang/proof/map/abilities.flang
→ утверждений 28: доказано 28 (из них индукцией 1)
(из них без теоремы 27, объявленным типом 7), сетка 0,
объявлено, не доказано 0
код возврата 0
Сторож на расхождение — bootstrap/flang io scripts/kernel-abilities-guard.flang. Он спрашивает на каждую строку четыре вещи: якорь стоит в ядре; номер умения стоит в этом файле; улика стоит в файле улик; строк списка столько же, сколько утверждений в уликах. Убери одну способность из описания — сторож краснеет с кодом 1 и называет её номером. Улика ДО работы: он краснел ровно 28 раз.
ПОРЧА, обе половины прогоном:
снял «У17.» из этого файла
→ У17: ОПИСАНИЕ отстало — умения «строгую положительность даёт только
названный факт «терм больше К» при К не меньше нуля» в docs/flang/proof/SPEC.md нет
разошлось мест: 1 код возврата 1
снял «У28» из файла улик
→ СЧЁТ разошёлся: строк списка 28, утверждений в уликах 27
разошлось мест: 1 код возврата 1
вернул обе
→ умений в списке: 28; ядро, описание и улики сошлись на всех код возврата 0
Перепись целей считает файл улик наравне с остальными: bootstrap/flang io scripts/targets/target-census.fscript --plan Census -- flang/proof/map даёт модулей измерено: 8, не измерено: 3; утверждений в разборе: 80; с вердиктом: 79 (было 7 и 51 — карта клеток осталась той же, прибавились ровно эти 28).
Правило 3, тождество: сличение НЕ синтаксическое
Здесь и в разделе 1.3 файла docs/specifications/proof-language.md стояло «стороны совпадают синтаксически». Это беднее правды на три умения.
У1. плюс сличается с точностью до порядка соседей ОДНОГО узла. Перестановка двух операндов одного узла — теорема IEEE-754, и ядро её знает. Без неё не доказывается «Сумма наоборот»: тело а плюс б, обещание результат равен (б плюс а).
У2. умножить на — так же. Улика — «Произведение наоборот»: тело а умножить на б, обещание результат равен (б умножить на а). Через скобки переставлять нельзя, и ядро этого не делает: ассоциативность в IEEE-754 ложна. (а плюс б) плюс в против а плюс (б плюс в) — отказ.
У3. Свёртка, растущая РОВНО на один. У неё начало обязано совпасть с основанием правой стороны, шаг — прибавлять к мере накопителя ровно единицу, а список свёртки — быть тем же самым, чья длина стоит справа. Улика — «Копия свёрткой»: тело свёртка элементы начиная с пустой список как акк и эл → добавить эл к акк, обещание (длина результат) равен (длина элементы).
Правило 3: законы ВСТРОЕННЫХ ФОРМ, переписывающие длина РАВНЫМ выражением
В таблице правил этого не было. Формы, у которых длина считается точно, ядро переписывает равенством — и цель закрывается тождеством без единого шага теоремы.
У4. длина (разложить Т на символы) есть длина Т. Разложение даёт ровно один элемент на знак, и «знак» здесь тот же самый, каким считает длина. Улика — «Знаки текста».
У5. длина (добавить Э к Л) есть длина Л плюс 1. Улика — «Дописать в конец»: обещание (длина результат) равен ((длина элементы) плюс 1).
У6. длина (приписать Э к Л) есть длина Л плюс 1. Улика — «Дописать в начало», обещание то же.
У7. длина (соединить А с Б) есть длина А плюс длина Б. Держится это на том, что у строки flang мера ОДНА — знаки; склейка, слившая бы два знака в один, у рантайма ОТКАЗЫВАЕТ, значит значения, о длине которого шла бы речь, не появляется вовсе. Улика — «Склейка строк». Список закрыт ДВОИЧНОЙ формой: соединить L по Р — другой узел, и равенства у него нет.
Правило 2, ограниченность: шесть источников потолка, а не два
У8. Длина головы строки у образца голова и хвост — ровно один знак. Случай голова и хвост тем и отличается от пусто, что строка непуста, и тем же одним знаком связывает её типизатор. Улика — «Первый знак»: тело — разбор строки, обещание (длина результат) не больше 1; закрывается ИНДУКЦИЕЙ ПО СТРОКЕ (см. ниже) с правилом ограниченности в шаге.
У9. Потолок берётся из допущения «не больше» ИЛИ «равно» той же или меньшей границы. Т равно Л говорит о Т больше, а не меньше; без сверки границы правило приняло бы Т равно 11 за доказательство «Т не больше 10», и потому граница сверяется. Улика — «Потолок через равенство».
У10. Точный тип сотых несёт тот же потолок, что неотрицательное. Улика — «Сотых под потолком»: обещание результат не больше 9007199254740991.
У11. И тысячных — тоже. Улика — «Тысячных под потолком».
У12. Разность, у которой уменьшаемое ограничено, а вычитаемое неотрицательно. Улика — «Разность под потолком»: требует «а под потолком» а не больше 100, тело а минус б при б: неотрицательное, обещание результат не больше 100.
У13. Остаток от деления неотрицательного ЦЕЛОГО на положительный целый литерал. Целость здесь несёт вес: 5.5 остаток от 2 равно 1.5, а это больше единицы. Улика — «Остаток под делителем».
СЛОЖЕНИЯ в этом правиле нет и не будет — сумма двух неотрицательное выходит за потолок законно. Это единственное различие между дном и потолком отрезка неотрицательное, и оно не описка.
Правило 1, неотрицательность: пять источников, которых в перечне не было
У14. Остаток от деления конечного целого неотрицательного на ненулевой целый литерал. Целость обещают точные типы: неотрицательное, целое, сотых, тысячных. Улика — «Остаток неотрицателен».
У15. КВАДРАТ — умножение терма на себя же, знак в знак. Дна ему не нужно: точное е·е неотрицательно у всякого конечного е, а округление к ближайшему знака не меняет. Нужна КОНЕЧНОСТЬ. Улика — «Квадрат конечного»: требует «е конечно» е не больше 9007199254740991, тело е умножить на е.
У16. Конечность даёт и ОГОВОРКА САМОЙ ЦЕЛИ — не ((Т минус Т) равен 0) или Ц, та самая приписка «пока это число». Ядро её читает и кладёт «Т конечно» в известное, а доказывает после этого Ц. Улика — «Квадрат с оговоркой»: без единого требует, обещание не ((е минус е) равен 0) или (результат не меньше 0).
У17. Строгую положительность не даёт ни один объявленный тип (ноль лежит и в неотрицательное) — её даёт только НАЗВАННЫЙ факт вида «терм больше К» при К не меньше нуля. После этого второму сомножителю хватает дна. Улика — «Строго положительное на дно»: два требует (а больше 0, а не больше 9007199254740991), тело а умножить на б при б: неотрицательное.
У18. Дно несёт не только неотрицательное, но и вес. Улика — «Груз неотрицателен».
ВЫЧИТАНИЯ в этом правиле нет и не будет: разность двух натуральных отрицательна уже на 2 минус 3.
Правило 6, порядок соседних: его в описании не было ВОВСЕ
Цель Л не убывает — «у каждой пары соседей списка Л первый не больше второго» — выводится, когда список Л собран так, что порядок соседей виден по построению. Сравнение соседей ядро НЕ решает само: оно отдаёт цель Г не больше В тем же правилам, которыми сводит всякий порядок.
У19. Список из нуля элементов: пар соседей нет вовсе, и «у каждой пары…» истинно пустым образом. Улика — «Пустой не убывает».
У20. Список из одного элемента — так же. Улика — «Один не убывает», тело [н]. Внимание: добавить н к пустой список этим случаем НЕ закрывается — короткий список читается по выписанной форме, а не по форме прибавления.
У21. приписать Г к Х — при выведенном «Х не убывает» И выведенном «Г не больше головы Х». Голова хвоста обязана читаться ПО ПОСТРОЕНИЮ: у приписать Г к Л с неизвестным Л головы нет, и «Г не больше головы Л» не выводится — на пустом Л такого сравнения не существует вовсе. Улика — «Пара по порядку»: требует «первый не больше второго», тело приписать первый к [второй].
У22. Выбор если У то А иначе Б — при выведенных ОБЕИХ ветвях, причём ветвь иначе закрывается ещё и ПРОТИВОРЕЧИЕМ: то же условие уже стоит допущением, значит ветвь недостижима. Улика — «Выбор при условии-допущении». Из «не (а не больше б)» ядро НЕ выводит «б не больше а»: на «не число» ложны обе стороны сравнения.
Ещё два случая правило называет в своём отказе, и они в список умений не взяты, потому что прогона под ними у меня нет: звено голова и хвост внутри ветви разбора (тем же разложением, что У21) и допущение «тот же самый список не убывает» знак в знак. Второе я мерил, и оно закрылось НЕ этим правилом:
тотальная функция «Тот же самый»
требует «вход не убывает» элементы не убывает
обеспечивает «…» результат не убывает
элементы
→ доказано … правило «цель есть допущение»
То есть до правила 6 цель не доходит — её снимает правило 7 раньше. Отказ ядра называет этот случай своим, и оба чтения совместимы; но в списке умений стоит только то, за чем стоит прогон, показывающий ИМЕННО это правило.
Правило 8, несовместимые допущения: его в описании не было ВОВСЕ
У23. Пара несовместимых допущений делает случай недостижимым при ЛЮБОМ виде цели. Сравнения приводятся к одной стороне (а больше б читается как б меньше а), после чего несовместимы ровно три вида пар, и во всех трёх ПЕРВАЯ строгая: а меньше б и б меньше а; а меньше б и б не больше а; а меньше б и а равен б. Улика — «Несовместимые допущения»: два требует (а меньше б, б не больше а), обещание результат не меньше 0 при теле 0 минус 1 плюс 1, которое само по себе неотрицательным не выводится.
Чего в списке нет и не будет: пары двух НЕСТРОГИХ (совместимы при а = б); пары а не больше б и а не равен б (совместимы при а < б); и вообще ничего, что требовало бы сложить два сравнения через третий терм — транзитивности у ядра нет.
Правило 4, порядок: случаев ШЕСТЬ, а не пять
Раздел 3в-бис ниже говорит «случаев пять, список закрыт». Шестой был добавлен позже и в перечень не попал.
У24. Прибавление к ГРАНИЦЕ неотрицательного конечного литерала при уже известном порядке: из Е не больше Г следует Е не больше К плюс Г. Это не то же, что пятый случай (прибавление ОДНОГО И ТОГО ЖЕ литерала к ОБЕИМ сторонам): здесь литерал прибавляется только справа. Улика — «Прибавка к границе»: требует «е не больше г», обещание результат не больше (5 плюс г) при теле е.
Правило 5, вхождение: в таблице правил его не было, а случаев четыре
У25. добавить Э к Л и приписать Э к Л при том же самом Э знак в знак. Улика — «Дописанное стоит».
У26. Они же при ЛЮБОМ другом первом аргументе, если Э стоит по построению уже в Л: ни добавить, ни приписать элементов не теряют. Улика — «Прежнее стоит»: тело добавить второе к (добавить первое к элементы), обещание результат содержит первое.
У27. Список, выписанный целиком и содержащий Э знак в знак. Улика — «Выписанный содержит», тело [а, б], обещание результат содержит а.
У28. Выбор, у которого Э стоит в ОБЕИХ ветвях. Улика — «Обе ветви содержат», тело если а меньше б то [а, б] иначе [б, а].
И у искомого обязана быть ЗАКРЫТАЯ форма: литерал-скаляр, конструкция, запись, выписанный список или имя, объявленный тип которого известен и не есть функция. Равенство значений вычислителя на функции ложно даже с самой собой, и правило, бравшее имя без объявления, доказало бы ложь о списке функций.
Что в этом файле было сказано ЛОЖНО, и это исправлено
Места, где описание обещало не меньше, а БОЛЬШЕ или ИНАЧЕ, чем ядро умеет, опаснее пропущенных: по ним человек строит теорему и упирается в отказ. Найдено четыре, и все четыре — про самого себя, а не про ядро:
- «Правил сведения три, список закрыт» (раздел 3б) и «Правил сведения стало четыре» (раздел 3в-бис). Правил восемь. Разделы оставлены как летопись, но число в них читать нельзя — читать таблицу выше.
- Ссылки на файлы реализации на JavaScript. Реализации на JavaScript в дереве нет: она удалена (48 файлов, 56 072 строки). Ядро живёт в
flang/self/proof-kernel.flang, и только там. - «Случаев пять, список закрыт» у правила 4 (раздел 3в-бис). Случаев шесть — см. У24.
- «Нужен симметричный близнец правила роста: „растёт РОВНО на один“» (раздел «Мера списка», перечень непокрытого). Близнец НАПИСАН — см. У3, и улика
«Копия свёрткой»доказывается прогоном. - «Индукцию по строке ядро не доказывает… алгебра ей не дана нарочно» (раздел «Граница честности»). Индукция по строке ЕСТЬ и работает: улика У8 получает вердикт
доказано индукцией по «строка»: база 1 случай, шаг при допущении на частях (1 случай). То же измерено вdocs/proof-goal-map.md(клетки К3 и К4). - «Ссылку
по свойствуядро не берёт — по-прежнему» (тот же раздел), при том что раздел «Ход седьмой: СЛИЧЕНИЕ ПО ВЫЗОВУ» пятью экранами выше говорит «Дождались». Файл противоречил сам себе. Берёт: прогонflang/proof/examples/body-forms.flangдаёт «Выше на два» —доказано: терм принят ядром, 1 шаг, а вся теорема там — один шагпо свойству. docs/specifications/proof-language.md§1.3: «устрокапринципа нет — проверено прогоном» и «сличение синтаксическое». Оба неверны, оба исправлены там же.docs/proof-goal-map.md: «правил у ядра шесть», причём шестым названо «цель есть допущение», а«не убывает»пропало вовсе. Правил восемь; исправлено.
Пункты 5–8 — того сорта, что опаснее пропущенных умений: описание обещает меньше, чем ядро умеет, и по такому описанию человек не пишет доказательства, которое прошло бы. Мест, где описание обещало БОЛЬШЕ ядра, при этой сверке не нашлось ни одного — ядро оказалось право везде.
Ядро — тотальная программа flang, и это показано прогоном
Свойство, ради которого ядро своё, а не чужое: проверяльщик термов — структурная свёртка по КОНЕЧНОМУ дереву, то есть идеальная тотальная программа flang.
Эталон ядра на самом языке лежит в flang/proof/kernel.flang. Что о нём говорит flang check --proof:
«Слабее» доказано композицией: рекурсии нет
«Вердикт шага» доказано композицией: рекурсии нет
«Вердикт цепочки» доказано структурой: аргумент 1 («звенья»)
на каждом витке становится частью себя
«Вердикт случаев» доказано структурой: аргумент 1 («случаи»)
на каждом витке становится частью себя
«Вердикт половины» доказано композицией: рекурсии нет
«Вердикт непокрытого» доказано композицией: рекурсии нет
«Вердикт индукции» доказано композицией: рекурсии нет
«Вердикт доказательства» доказано композицией: рекурсии нет
«Вердикт доказательства индукцией» доказано композицией: рекурсии нет
итог: функций 9: тотальных 9, обычных 0; сторожей в рантайме: 0 мест
Свёртка принципа индукции живёт здесь же, на самом языке, и это условие работы, а не украшение: припиши кто-нибудь свёртку принципа только в свидетеля — сверка молча стала бы проверять половину ядра. Индукция при этом не потребовала ни одного сторожа: четыре новые функции не рекурсивны вовсе, а две прежние как убывали структурно, так и убывают. Если бы принцип потребовал счётчика глубины или предела шагов, это значило бы, что сделано не то.
Носитель обещания у рекурсивных функций — СТРУКТУРА, и это сильнее, чем просто «тотальна»: с мерой или постоянным шагом тотальность тоже бывает, но со сторожем, а сторож — место, где программа честно откажет вместо ответа. У свёртки по конечному дереву такого места нет. Ядро печатается во все восемь целей.
Вердикты эталона сверены с вердиктами свидетеля на 3282 порождённых термах цепочек и 133 956 термах принципа индукции, расхождений 0 (сверка ядра со второй реализацией) — тем же приёмом, которым в этом дереве сверяются девять исполнителей.
У сверки принципа прочтений три, а не два: эталон на flang, свидетель, записанный в тесте заново, и свернутьПринцип из ядра. Третье не лишнее — ядро сворачивает принцип по ВЕРДИКТАМ посылок, а эталон по пометкам шагов, и не сойдись они, ведомость печатала бы «доказано индукцией» там, где эталон сказал бы «отвергнуто».
Что эта сверка не доказывает. Она не доказывает состоятельности ядра. Совпадение двух реализаций ловит ошибку в одной из них; общая ошибка проекта останется общей. Поэтому о результате говорится ровно с той силой, какая у него есть: «перекрёстно сверено на N термах», а не «ядро состоятельно». Это та же дисциплина, по которой в ведомости запрещено слово «проверено» вместо «доказано».
Эталон есть и у РЕШАЮЩЕЙ половины, а не только у свёртки пометок
flang/proof/kernel.flang выше сворачивает уже расставленные ПОМЕТКИ шагов в вердикт. Кто расставил пометки — то есть кто решил, выведен шаг или нет, — до этой работы оставалось на JavaScript целиком: 1306 строк, начальная алгебра (399) и сведение (907). Это и есть то место, где решается, что считается доказательством, и эталона у него не было.
Теперь есть, обеими половинами:
| эталон | свидетель | сверка | чем сверено |
|---|---|---|---|
self/proof-kernel.flang (142 функции) | ядро на JavaScript (3022 строки) | сверка ядра с двойником | 15 828 утверждений: 7888 порождённых, 16 настоящих посылок корпуса, 580 на объявленных типах, 7040 на правиле потолка, 288 на развёртке определений и 16 на замкнутом круге (принцип строит эталон, сводит эталон); расхождений 0 |
self/proof-initial.flang (73 функции) | начальная алгебра на JavaScript (1547 строк) | сверка начальной алгебры | 410 входов побайтово + принцип целиком на 6 доказательствах корпуса (14 посылок, 7 допущений), расхождений 0 |
Эталон знает ВСЕ ТРИ решающих правила и ВСЕ ЧЕТЫРЕ переписки нормализации, и это стоило отдельной работы: на первом переносе он унёс два правила и три переписки, а зазор был измерен прогоном — правило потолка расходилось на 450 утверждениях из 2268, развёртка на 27 из 144. Оба числа теперь ноль.
Критерий у первой сверки не «принято/отвергнуто», а вердикт, имя правила, текст причины и отчёт о развёрнутом — знак в знак. Довод тот же, по которому печать сверяется побайтово: два ядра могут принять одно и то же РАЗНЫМИ правилами и разойтись на первом же утверждении, которого в корпусе не было. Имя правила и текст причины уезжают в отказ к автору доказательства; разойдись они, автор чинил бы не то, что сломано.
Подделка отвергается эталоном так же, как свидетелем — тем же правилом и тем же текстом. Проверено обеими подделками: тело шага, вычитающее вместо сложения (правило 1), и функция, объявленная тождественной и таковой не являющаяся (правило 2).
И каждое правило доказано изъятием, а не обещанием. Шесть подмен в копии эталона: сложение в правиле потолка (оно доказало бы ЛОЖЬ о «Сумме пары» из корпуса), снятое сравнение границы с потолком типа («неотрицательное» стал бы десяткой), снятое «только тотальное», снятая проверка захвата имени, замолчанный рассказ о достигнутом пределе и потерянная протяжка состояния нормализации. Сам предел изъять нельзя: без него проверка не покраснела бы, а ЗАВИСЛА.
Круг замкнут на том куске пути, который перенесён. Принцип строит эталон, сводит его эталон, и вердикт каждой из 16 посылок корпуса совпадает с вердиктом двух свидетелей на том же входе. Проверка отдельная от сверки вердиктов, и это не дублирование: сверке вердиктов термы даёт СВИДЕТЕЛЬ, поэтому она молчала о том, что эталон начальной алгебры не строит поля declarations вовсе. Нашла это побайтовая сверка принципа, а закрывает пятно третья проверка.
СВЯЗКА ПЕРЕНЕСЕНА, И ЭТО НАЗВАНО ЧИСЛОМ. Здесь стояло «между поверхностью и двумя перенесёнными половинами стоят ещё 1281 строка JavaScript». Стоят ноль:
| эталон | свидетель | сверка | чем сверено |
|---|---|---|---|
self/obligations.flang (44 функции) | обязательства на JavaScript (301 строка) | сверка обязательств | 195 программ (всё дерево и 13 нарочно дурных), 128 обязательств, 11 диагностик; JSON.stringify знак в знак, расхождений 0 |
self/proofterm.flang (94 функции) | ядро на JavaScript (2380 строк) | сверка ядра с двойником | 201 программа (всё дерево, 17 нарочно дурных и две собранные руками), 69 строк ведомости, 18 диагностик, вердиктов «доказано» 50, «сетка» 1, «отвергнуто» 18; JSON.stringify знак в знак, расхождений 0 |
Критерий у обеих сверок побайтовый, и это не перестраховка. Обязательство — ДАННЫЕ, которые читает ядро (goal, bind, vars, hypotheses, forall): ошибись эталон в одном поле, и ядро честно свело бы ДРУГУЮ цель и честно сказало бы «доказано» о том, чего автор не обещал. У вердикта же сверяются и код отказа, и его ТЕКСТ: код уезжает в инструмент, текст к автору доказательства, и разойдись любой из двух — автор чинил бы не то, что сломано.
Отказы сверены не на дереве, а на программах, написанных под каждый отказ. В дереве нет ни одной теоремы, доказывающей не то, и ни одного невыводимого шага — и не должно быть. Поэтому корпус сверки дописан руками: семь кодов связки (NO_GOAL, AMBIGUOUS, DUPLICATE, CLAIM_MISMATCH, UNKNOWN_VAR, VAR_TYPE, UNFINISHED) и пять кодов ядра (STEP, INDUCTION_CASES, INDUCTION_STEP, INDUCTION_TYPE, INDUCTION_BRANCH), и отдельная проверка требует, чтобы все двенадцать на корпусе СРАБОТАЛИ: «обе стороны молчат» — тоже совпадение, и принимать его за сверку нельзя.
Одно место, где дословный перенос НЕВОЗМОЖЕН, и оно названо причиной. Исход прогона примера (по примеру «имя») приезжает в эталон ДАННЫМИ, а не считается им. Причин две, и обе — свойства языка: исключений в flang нет вовсе (свидетель ловит ошибка.message интерпретатора JavaScript), а «Прогон» вычислителя на flang объявлен ОБЫЧНЫМ намеренно — вычисление чужой программы завершаться не обязано, а нетотальность заразна по вызову, и слой, печатающий «доказано», звать его не вправе. Приём тот же, каким в self/proof-kernel.flang приезжают определения функций. Все остальные решения — сличение случаев с конструкторами, раздача посылок правилам, вызов сведения, свёртка пометок в вердикт — принимает эталон.
Что это меняет и чего не меняет. Меняет: слова «доказуемость при наличии Node» больше не обязательны — вся цепочка от постусловия до вердикта существует на самом flang. Не меняет: ни одно ПРЕЖНЕЕ утверждение корпуса не сменило вердикта и не сменило слов — индукцией по-прежнему 8, по объявленному типу 3, сеткой 1, объявлено и не доказано 1, шагов в принятых термах 18, отвергнутых 0. Перенос ничего не доказал заново.
Прибавилось ровно два утверждения (31 → 33 высказано, 29 → 31 доказано), и оба — следствие ПРАВИЛА КОРПУСА, а не переноса правил. Каждый слой эталона называет версию своего формата нуль-арной функцией с литералом, ядро сводит у такой функции «результат не меньше 0», а проверка утверждений набора требует, чтобы такая функция в каталогах побайтовой неподвижной точки несла утверждение там же, где стоит. Два новых слоя дали две новые версии формата — и два утверждения при них.
Свидетель ушёл вперёд, пока писался эталон, и зазор ИЗМЕРЕН. Эталон писался под свидетеля своего основания. Пока он писался, сборщик влил в main работу над САМИМ ядром: свидетель ядра там вырос на 443 строки, и прибавка не косметическая — у принципа индукции появился ТРЕТИЙ источник (отрезок объявленного неотрицательное), у встроенного списка появилась начальная алгебра, у отказов прибавился код descent, в правила сведения вошло умножение при условии.
Мерено прогоном, а не оценено: на корпусе main (189 программ, 44 строки ведомости) вердикт эталона расходится со свидетелем main на ЧЕТЫРЁХ программах, и ещё на ШЕСТИ решение то же, а текст или поля разошлись. Все десять — про то, чего в основании не было вовсе: индукцию по числу и по встроенному списку. У self/obligations.flang зазора нет вовсе: обязательства на JavaScript на main отличается одним словом export.
Это та же развилка, которую однажды прошёл self/proof-kernel.flang («свидетель раздвоился, и это названо числом»), и решение то же: эталон не притворяется, будто свидетель один. Догонять два расходящихся свидетеля разом — значит не догнать ни одного.
НАЧАЛЬНАЯ АЛГЕБРА ВСТРОЕННОГО СПИСКА ДОГНАНА. Мерено прогоном: до переноса сверка ядра с двойником показывала 250 программ, совпало побайтово 236, названный зазор 14; после — 251 программа (прибавилась подделка), совпало 240, зазор 11. Три программы ушли из зазора поимённо: proof/examples/corpus-hof.flang, corpus-length.flang, corpus-lists.flang.
Перенесено ровно то же чтение, каким его делает свидетель, и ни одного правила сверх: объявление списка эталон собирает сам («Объявление списка» в self/proof-initial.flang — те же два конструктора, тот же функтор F(X) = 1 + (Э × X)), имена конструкторов читает ОДНО место на оба слоя («Есть конструктор у образца» и «Имя конструктора образца» в self/proof-kernel.flang, эталоны образца варианта из flang/self/proof-kernel.flang), а место X узнаёт сравнением ТИПА, а не имени: у встроенного списка имени нет вовсе, и хвост опознаётся равенством хвост: список Э самому разбираемому типу.
Держат это красным три вещи, а не обещание: подделка «длина положительна» (ложь ровно в базе принципа — на пустом списке; отвергается обоими одним кодом и одним текстом), изъятие «начальная алгебра встроенного списка перестала читаться» и изъятие «конструктор значения примера перестал узнаваться у списка» — оба краснят сверку сверх названного зазора.
ТОЧНЫЕ ДЕСЯТИЧНЫЕ И ПРАВИЛО ОСТАТКА ДОГНАНЫ. Мерено тем же прогоном: 251 программа, совпало 240, зазор 11 — стало 252 программы (прибавилась вторая подделка), совпало 242, зазор 10. Из зазора ушла docs/examples/money/exact-decimal.flang.
Вопросов к подписи у эталона стало ТРИ, и списки у них разные — ровно как у свидетеля. Дно: «неотрицательное», «вес», «сотых», «тысячных». Потолок: «неотрицательное», «сотых», «тысячных» (у «вес» потолка нет вовсе — +∞ там значение, а не край). ЦЕЛОСТЬ: «неотрицательное», «целое», «сотых», «тысячных» («целое» обещает её, не обещая знака). На третьем списке стоит правило остатка, у которого две посылки и обе несут вес: делимое неотрицательно и КОНЕЧНО ЦЕЛО, делитель — ненулевой целый литерал.
Сверено перебором, а не подбором: шесть имён типов на восьми целях — 48 целей, принято свидетелем 13, расхождений 0 (сверка ядра с двойником, «ТОЧНЫЕ ДЕСЯТИЧНЫЕ И ОСТАТОК»). Держат это красным подделка «остаток неотрицателен при делимом типа «число»» (та же теорема, что доказана на сотых, но написанная на число; оба ядра отвергают её одним текстом) и изъятие целости с контрпримером на вес: у него ДНО есть, а целости и конечности нет, и +∞ остаток от 100 есть не-число.
ПРИНЦИП ПО ОТРЕЗКУ «неотрицательное» ДОГНАН. Мерено тем же прогоном: 252 программы, совпало 242, зазор 10 — стало 255 программ (прибавились три дурные), совпало 248, зазор 7. Из зазора ушли proof/examples/corpus-factorial.flang, proof/examples/segment.flang и дурная «FLANG_PROOF_INDUCTION_TYPE».
А ЭТО — ЧИСЛО ПОСЛЕ СЛИЯНИЯ, И ОНО НЕ РАВНО НИ ОДНОЙ ИЗ ТРЁХ СТОРОН. Три замера выше сняты на дереве переноса; в своде рядом с ним лежала ещё и типизация гипотезы дано, а с work/bez-fts в корпус приехали две программы docs/examples/library-api. Прогон на СЛИТОМ дереве даёт 257 программ, совпало 248, зазор 9 — у ветки переноса стояло 255/248/7, у дерева до первого слияния 249/237/12, у свода 250/236/14. Прибавка зазора — не откат: обе новые программы расходятся ровно ПЯТЫМ ходом, уже названным выше, а не новой причиной.
Носителей у принципа стало ДВА, и держатся они на разном. Сумма — на НАЧАЛЬНОСТИ: значение построено конструкторами, часть меньше целого по построению. Отрезок — на КОНЕЧНОСТИ: неотрицательное есть [0, 2⁵³−1], спуск идёт ровно на единицу, соседние целые внутри отрезка представимы точно, и цепочка спусков дна не проскакивает. Читаются те же пять мест, что у свидетеля («Принцип по числу» в self/proof-initial.flang): тип переменной, мера (убывает), условие дна, какая ветвь база и на чём стоит рекурсивный вызов. Свёртка принципа при этом ОДНА на оба носителя — ветки «а если по отрезку» в ядре нет ни одной.
Сверено на девяти входах: три настоящих принципа корпуса и шесть нарочно непрочитаемых, по одной программе на каждый вид unread (не параметр, нет меры, тело не «если», дробное дно, спуск на два, база зовёт себя, шаг не зовёт себя); расхождений 0. Кодов отказа у ядра стало шесть: прибавился FLANG_PROOF_INDUCTION_DESCENT, и он не общий со «шагом» нарочно — «шаг не сведён к допущению» и «спуск, на котором стоит допущение, никуда не спускается» — разные находки и разные починки.
Держат это красным две подделки и три изъятия. Подделка «спуск на два проскакивает дно»: «Через один» от 1 считает «Через один» от −1, тот уходит в базу и даёт 0, а результат −1 — рантайм отвергает это FLANG_PROPERTY на н = 1. Подделка «дробное дно»: если н меньше 2.5 при н: неотрицательное отправляет в базу и 2, а верх дна считался бы как 1.5 — ядро на этой самой записи когда-то печатало «доказано». Изъятия: принцип по отрезку перестал читаться; носителю подсунут функтор вместо отрезка; пример перестал требовать попадания в дно. И отдельно — изъятие строгого спуска в сверке начальной алгебры: разреши н минус 2, и эталон строит принцип на проскоке дна.
Что ещё держит свидетель, названо там же, где считается: flang check --proof (командная строка на JavaScript) зовёт JavaScript-половины, а не эталонов; перевод AST в «Значение» и обратно живёт в сверках, а не в дереве; и восемь целей печати эталонов связки пока не печатают.
Два места, где дословный перенос дал бы ПРОТИВОПОЛОЖНЫЙ ответ. Свидетель сличает узлы через ===; -0 === 0 истинно, а равен языка работает через Object.is и говорит «нет». NaN === NaN ложно, а Object.is(NaN, NaN) истинно. Ноль цели «Е не меньше 0» сличается именно этим сравнением, поэтому эталон сличает числа двойным «не меньше», а не равен. Оба места стерегутся изъятием.
Найдено сверкой, и это про корпус, а не про эталон. Порядок двух подстановок в заключении посылки назван значимым здесь и в шапке flang/self/proof-initial.flang (тело ветви вправе упоминать переменную индукции), но входа с такой записью в proof/examples/ НЕТ НИ ОДНОГО: изъятие внутренней подстановки проходит незамеченным на всех шести доказательствах корпуса. Пример пришлось написать в тесте.
Сторож на класс: «доказано обо ВСЕХ входах» против прогона
Сверка эталонов состоятельности не доказывает — это сказано абзацем выше и остаётся правдой. Есть и вторая причина завести проверку другого рода: точечный тест ловит ТУ дыру, которую видел его автор. За одни сутки ядро ШЕСТЬ раз напечатало «доказано» о неправде, и каждый раз дыра была новая:
- дробное дно отрезка (
work/kernel-factorial); - незамкнутая цель под ПРЯМЫМ
по примеру(замерwork/zamer-tseny); - свободный ВТОРОЙ параметр в базе индукции — та же проверка, другой вход;
- захват имени под
отфильтроватьпри РАЗВЁРТКЕ определения; - захват имени при ПОДСТАНОВКЕ цели, без всякой развёртки;
- тот же захват через ИНДУКЦИЮ — «доказано индукцией … обо ВСЕХ входах типа «Дерево»» об утверждении, которое рантайм отвергает на первом же значении.
Каждая закрыта своим тестом, и каждый такой тест смотрит ровно туда, куда смотрел его автор.
Общего у всех шести ровно одно, и это не место: проверка состоятельности БЫЛА, а один вход в неё её не звал. Прямой шаг не заходил в ветку случая; проверка случая смотрела на образец, а не на заключение; сторож захвата знал три связывателя из четырёх; подстановка стерегла имя ключа и не смотрела на имена значения. Место у каждой своё, класс один — и сторож написан на класс.
Поэтому у слоя есть проверка не на место, а на СВОЙСТВО (проверка непротиворечивости):
если ведомость говорит об утверждении «обо ВСЕХ входах», то прогон не находит контрпримера ни на одном значении враждебной сетки.
В этом условии нет ни слова про по примеру, про индукцию, про правила сведения и про чтение объявленного типа: новое правило ядра попадёт под него само, без единой правки в тесте.
Сетка читается с объявлений, а не угадывается. Скаляры — враждебной выборкой (ноль и минус ноль порознь, NaN, обе бесконечности, дробное, пустая строка); объявленные суммы — тем же чтением конструкторов, каким порождается принцип (flang/self/proof-initial.flang). У неотрицательное сетка лежит ВНУТРИ отрезка [0, 2^53−1]: значение вне него рантайм отвергает FLANG_TYPE («вне неотрицательное»), и контрпримером оно не является — утверждение о нём и не высказывалось. Функция-значение приезжает из примеров автора: годятся только теги, которые программа где-то берёт значением.
Контрпример отличается от прочих отказов по СООБЩЕНИЮ, а не по коду. FLANG_PROPERTY носит и чужое постусловие, всплывшее из вложенного вызова; а «предел шагов», «вне неотрицательное» и «встроенная форма на пустой строке» говорят, что вычисление не дошло, — это не опровержение.
Проверяются ВСЕ утверждения корпуса, а не только доказанные. Сперва сторож отбирал по вердикту, и довод к отбору был верен про вердикт и неверен про утверждение: «сетка» об ВСЕХ входах ничего не обещает — но само утверждение под ней либо правда, либо нет. Разницу показал корпус. В four-words.flang — файле, который УЧИТ читать слова ведомости, — два утверждения были ложны («результат не меньше 0» о функции над обычным число, контрпример −1), а ведомость печатала «сетка 3 значения: нарушений не найдено» и «объявлено, не доказано». Обе строки правда о себе, а пара читается как заверение. Нашли это независимо ветка work/trebuet чтением и сторож прогоном.
Поэтому вердикт теперь не отбирает, а НАЗЫВАЕТСЯ рядом с находкой: по нему читается, что чинить — «доказано» о лжи есть дыра в ядре, «сетка» о лжи есть неправда в самом утверждении. Порог у обоих один: контрпримеров ноль.
Числа: утверждений корпуса 60, покрыто сеткой 60, значений прогнано 2121, опровергнутых 0. Охват стоит числом и краснеет: проверка вида «для всех верно X» зеленеет и при нуле проверенных, а непокрытый параметр называется поимённо.
Зубы показаны отдельно. На всех ПЯТИ подделках — прямой шаг при цели с параметром, база индукции при свободном втором параметре, захват имени при развёртке под отфильтровать, захват при подстановке цели и он же через индукцию — сетка контрпример НАХОДИТ. Изъятие каждой проверки ядра красит поимённо и ничего кроме: снят запрет прямого шага — краснеют три («ПОДДЕЛКА ПРЯМАЯ», «ВСЕ ЧЕТЫРЕ ОБОСНОВАНИЯ», «гипотеза «дано»»); снято поле посылка.free — одна («ПОДДЕЛКА ИНДУКЦИЕЙ»); снят четвёртый связыватель у сторожа захвата — одна («ПОДДЕЛКА ЗАХВАТОМ»); возвращено ложное утверждение в витрину — одна («КЛАСС»), и в отказе стоят файл, имя, вердикт и контрпример.
Чего сторож не покрывает, и это надо сказать. Файл с ОТВЕРГНУТЫМ термом в корпус попасть не может: flang check его не пропустит, и свод требует нуля отвергнутых. А файл с ошибочно ПРИНЯТЫМ термом пропустится и в корпусе окажется — это ровно тот случай, ради которого сторож и написан. Сетка при этом конечна, и молчание сторожа доказательством не является — по той же причине, по какой им не является «сетка N» в самой ведомости. Сторож несимметричен нарочно: найденный контрпример окончателен, ненайденный не говорит ничего.
Слова ведомости: их стало пять
flang check --proof теперь различает:
- доказано — утверждение обо ВСЕХ входах: терм принят ядром (или завершение выведено анализом);
- доказано индукцией по «Т»: база N случаев, шаг при допущении на частях — то же самое и строго больше: носитель утверждения БЕСКОНЕЧЕН, и покрыт он не перечислением, а принципом, прочитанным с объявления суммы «Т». Слово пятое, а не оттенок четвёртого, и путать их нельзя ровно по той же причине, по которой нельзя путать «доказано» и «сетка»: первое говорит «цель свелась к названным фактам», второе — «цель верна на носителе, который нельзя перебрать».
Отличать его надо и от «доказано структурой» из раздела «чем несётся обещание „тотальная“»: там речь о ЗАВЕРШЕНИИ функции, здесь — об истинности утверждения о её результате. Одно без другого бывает сплошь и рядом: функция «Высота» была доказана структурой с самого начала, а «высота неотрицательна» стояло сеткой до этой работы;
- сетка N значений: нарушений не найдено — прогнано на N значениях автора и нарушений на них не нашлось; это НЕ доказательство. «Не найдено» означает здесь «ИСКАЛИ и не нашли», и это не подразумевается, а исполняется: примеры прогоняет
flang/self/grid.flangтем же вычислителем, каким работаетflang test. Когда прогона не было или он не дошёл — сказано «нарушений НЕ ИСКАЛИ», теми же словами; - НАРУШЕНО на примере «…» — прогон нашёл вход автора, на котором утверждение ЛОЖНО. Слово шестое, и оно не про степень уверенности, а про факт, поэтому в словаре вверху ведомости его нет. Оно бьёт «доказано» и «сетку» (при «доказано» расходятся не два мнения, а доказательство и факт — это ошибка ядра) и ДОБАВЛЯЕТСЯ к «отвергнуто ядром», где оба факта смотрят в одну сторону;
- объявлено, не доказано — утверждение высказано, доказательства при нём нет вовсе. Сюда попадает старая форма
теоремаи постусловие без теоремы и без примеров; - на веру — не посчитано ничем.
Разница между двумя последними содержательная: «на веру» — допущение, которое считать НЕЧЕМ (изоморфизм без даёт); «объявлено, не доказано» — утверждение, которое считать есть чем, а никто не считал. Первое требует новой проверки, второе — доказательства от автора.
Слова «проверено» в ведомости нет и не будет: оно и есть то, которое читается двумя способами. Запрет держится тестом.
Что перешло из сетки в доказанное, поимённо и с числами
Свод по корпусу (flang/scripts/proof-ledger.mjs) до индукции и сегодня:
| было | сегодня | |
|---|---|---|
| утверждений высказано | 4 | 9 |
| доказано ядром | 2 | 6 |
| из них индукцией по объявленной сумме | — | 6 |
| сетка (примеры без теоремы) | 1 | 2 |
| объявлено, не доказано | 1 | 1 |
| отвергнуто | 0 | 0 |
Столбец «стало» описывает ТОТ ШАГ, а не сегодняшнее дерево, и путать их нельзя: свод растёт каждый день, а таблица шага — запись о работе, которую сделали. Что свод говорит про дерево на 15 августа 2026, после второй волны сборки: высказано 28 утверждений, доказано ядром 26 (из них 8 индукцией по объявленной сумме и 18 сведением цели с телом, а внутри этих шестнадцати ровно 3 опираются на объявленный тип аргумента), сетка 1, объявлено-не-доказано 1, отвергнуто 0, нарушено 0, шагов в принятых термах 18. Числа эти руками не сверяются: они стоят равенствами в проверке отчёта о доказательствах и сличаются с прозой docs/overview.ru.md сторожем flang/scripts/count-guard.mjs.
ЧЕТЫРЕ УТВЕРЖДЕНИЯ ПРИШЛИ ОБХОДОМ КОРПУСА, А НЕ РАБОТОЙ НАД ЯДРОМ (work/claims-core), и цель у них ДРУГАЯ — верхняя граница, а не знак: «разность пары не выходит за точный потолок» (docs/examples/measure/natural.flang, правило ограниченности, факт даёт объявленный неотрицательное) и «значение цифры не больше тысячи» — на трёх поверхностях римских цифр разом (docs/examples/leetcode/013, docs/examples/rosetta/roman-numerals.flang и её английский двойник). Первое переехало К ФУНКЦИИ из дословной копии corpus-natural-ceiling.flang: долг парсера, из-за которого копия заводилась, закрыт, а копия осталась на месте. У «Значения цифры» стало ДВА утверждения — первый такой случай в дереве: вопроса два, знак результата и его потолок, а формы «результат от 0 до 1000» у языка нет. Свод сегодняшнего дерева от этого стал 146 / 119.
А ЧТО ИНДУКЦИЯ К ФУНКЦИЯМ КОРПУСА ПЕРЕЕХАТЬ НЕ МОЖЕТ — БОЛЬШЕ НЕПРАВДА. Довод стоял такой: у «Глубины» из docs/examples/leetcode/104-maximum-depth-of-binary-tree.flang ядро говорило «доказано индукцией по «Дерево»», разбор эталоном сходился побайтово, а краснела сверка СВЯЗАННОГО AST — связанную программу самоприменённый компилятор собирал из пяти ключей (flang, module, types, functions, legacy), и theorems среди них не было: теорема доезжала до разбора и терялась на связывании. Ключ написан («Теоремы входа» в flang/self/bootstrap/compiler.flang), и сверка стоит исполняемой в сверке раскрутки («теорема входного файла едет в связанную программу», и обратной половиной — «без теоремы ключа theorems в программе нет»). Помехи этой больше нет.
Три из семи — о настоящих функциях корпуса, стоявших там задолго до слоя доказательства:
| функция | откуда | утверждение | было | стало |
|---|---|---|---|---|
| «Высота» | flang/stdlib/numtree.flang | «высота неотрицательна» | сетка 3 значения (примеры функции) | доказано индукцией по «Дерево чисел»: база 1 случай, шаг при допущении на частях (1 случай) |
| «Размер дерева» | flang/stdlib/tree.flang | «размер неотрицателен» | сетка 2 значения | доказано индукцией по «Дерево» |
| «Глубина» | docs/examples/leetcode/104-… | «глубина неотрицательна» | сетка 1 значение | доказано индукцией по «Дерево» |
Первое, что надо сказать честно: доказательства лежат НЕ У ФУНКЦИЙ. Они лежат в flang/proof/examples/corpus-numtree.flang, corpus-tree.flang и corpus-depth.flang, а тела функций перенесены туда ДОСЛОВНО. Причина была такая: у девяти слов слоя доказательства нет продукции в flang/self/parser.flang, а flang/stdlib, flang/core, docs/examples/leetcode, docs/examples/measure и flang/self входят в корпус побайтовой неподвижной точки самоприменения ЦЕЛИКОМ. Напиши обеспечивает у библиотечной функции — и парсер на flang перестанет давать тот же AST, что свидетель. Это было проверено падением: сначала утверждения написались прямо в библиотеке, сверка разборщиков покраснела на побайтовой сверке, и работа откатилась туда, где ей место.
Эта причина СНЯТА, и держать её записанной значило бы запрещать сделанное. Продукции написаны все девять: восемь — 15 августа 2026, девятое (требует) — при слиянии work/trebuet. Записей в ПРОБЕЛЫ_РАЗБОРА у них нет ни одной, а ПРОБЕЛЫ_РАЗБОРА снимаются с таблицы лексера прогоном, а не пишутся по памяти: там одна строка, inMonad, и у неё причина другая. Правило «flang/stdlib не имеет права ими пользоваться» из запрета переделано в СЧЁТ (проверка ядра): слово разрешено и обязано стоять ровно там, где вписано, — и flang/stdlib/numbers.flang, higher-order.flang, logic.flang, strings.flang в этом списке уже стоят. Запрещено одно слово, по свойству, и не за разбор, а потому что ядро отвергает его всегда.
Что держит эти три файла на месте сегодня — другое. Постусловие переезжает к функции даром: оно живёт полем функции и едет вместе с ней. ТЕОРЕМА не едет — её берёт сборка программы, а не чтение файла, — и перенос теорем к функциям есть отдельная работа со своим прогоном, а не следствие продукции.
Второе: дословность переноса проверяет машина, а не глаз. проверка ядра сличает дерево разбора тела, подписи и объявления типа с файлом корпуса и краснеет на любом расхождении. Разойдись хоть что-нибудь — и «утверждение корпуса» стало бы утверждением о похожей функции. Тела при этом не тронуты ни на знак; единственная добавленная строка — пример базового случая у «Глубина», которого в оригинале нет (там примеры стоят у соседней функции).
Третье: сами постусловия написаны этой работой, и иначе быть не могло — слово обеспечивает появилось предыдущей работой, и в корпусе не было ни одного постусловия за её пределами. Поэтому «было» — это состояние С постусловием и БЕЗ теоремы, и оно измерено прогоном ведомости до того, как теоремы написались, а не восстановлено по памяти.
Сторож на будущее. Чтобы соблазн написать обеспечивает прямо в библиотеке не стоил следующему сорока минут полного прогона, в в проверке ядра стоит проверка: ни один файл корпуса самоприменения не употребляет слов доказательства.
Ещё два утверждения («штраф неотрицателен» светофора и «ступень неотрицательна» из four-words.flang) перешли с четвёртого слова на пятое: они и были доказаны, но теперь ведомость называет, ЧЕМ. Шестое — новое, в flang/proof/examples/stack.flang: «высота неотрицательна». Второе утверждение того же файла, «копия равна оригиналу», доказанным НЕ является и стоит сеткой одного примера — по причине, названной в самом файле и в таблице выше.
То же самое, шагом позже: начальная алгебра ВСТРОЕННОГО списка
| было | стало | |
|---|---|---|
| утверждений высказано | 30 | 30 |
| доказано ядром | 6 | 9 |
| из них индукцией по начальной алгебре | 6 | 9 |
| сетка (примеры без теоремы) | 23 | 20 |
| объявлено, не доказано | 1 | 1 |
| отвергнуто | 0 | 0 |
| шагов в принятых термах | 14 | 20 |
Три новых — первые утверждения языка о встроенном списке, и все три о настоящих функциях библиотеки:
| функция | откуда | утверждение | было | стало |
|---|---|---|---|---|
| «Длина» | flang/stdlib/lists.flang | «длина неотрицательна» | сетка 3 значения | доказано индукцией по «список» |
| «Индекс» | flang/stdlib/lists.flang | «индекс неотрицателен» | сетка 2 значения | доказано индукцией по «список» — на функции ДВУХ параметров |
| «Позиция где» | flang/stdlib/higher-order.flang | «позиция неотрицательна» | сетка 2 значения | доказано индукцией по «список» — первый параметр САМ ФУНКЦИЯ |
Второе и третье стоят здесь не для полноты. В lists.flang функций с двумя параметрами 19 из 28, и «второй параметр в заключении» — ровно то место, где правило по примеру до этой работы принимало ложь (см. «Граница честности»). Третье снимает подозрение, что функция-параметр индукции мешает: она проезжает посылку коэффициентом, как всякое поле не своего типа. Мешает она только тогда, когда заключение её УПОТРЕБЛЯЕТ, — у «Отобразить» и «Отфильтровать» так и есть, и они остались сеткой.
Число доказанных выросло на три, а число ПРИЧИН, по которым остальное не доказано, — с одной до трёх, и это второй результат той же работы. Раньше все пятнадцать утверждений о lists.flang стояли под одной строкой «начинать не с чего»; теперь 15 = 2 доказано + 8 (тело — свёртка или встроенная форма) + 4 (вид цели) + 1 (развёртка определения в базе).
Чего этой работой не сделано и почему. Шесть строк корпуса по-прежнему стоят сеткой с пределом — законы двух монад, изоморфизма, двух вложений и общей части (docs/examples/monad/order-total.flang, flang/ct/*.flang). Индукция их не берёт, и это не недоделка, а другая задача: два закона монады из трёх квантифицированы по СТРЕЛКАМ Клейсли (то есть по функциям, а не по значениям), а правая единица и кругооборот изоморфизма упираются в посылку со связанными именами нерекурсивных полей — тот самый случай, который закроет упрощатель. Обещать их индукцией значило бы обещать не то.
И ещё одно, уже объявленным типом, — с числами
У всех девяти слов есть продукция в самоприменённом парсере (flang/self/parser.flang). Таблица ключевых слов в flang/self/lexer.flang их уже содержит — она сверяется со свидетелем слово в слово, — и разбор к ним есть: восемь слов закрыты 15 августа 2026, девятое (требует) — при слиянии work/trebuet. Записи в ПРОБЕЛЫ_РАЗБОРА (сверка разборщиков) у них нет ни одной, а сверка AST со свидетелем идёт побайтово на всём каталоге.
Здесь стояло: «„Сколько дополнить“ лежит в flang/proof/examples/precondition.flang, а не в flang/stdlib/strings.flang, где ей место, потому что библиотека слов слоя доказательства употреблять не имеет права». Правдой это быть перестало: функция ПЕРЕЕХАЛА в библиотеку вместе со своим постусловием (work/stdlib-grow), и flang/stdlib/strings.flang:572 несёт обеспечивает «дополнение не выходит за точный потолок». Копия в precondition.flang осталась не долгом, а показом предусловия требует.
Свод по корпусу до этой работы и после (flang/scripts/proof-ledger.mjs):
| было | стало | |
|---|---|---|
| утверждений высказано | 9 | 10 |
| доказано ядром | 6 | 7 |
| из них индукцией по объявленной сумме | 6 | 6 |
| из них по объявленному типу аргумента | — | 1 |
| сетка | 2 | 2 |
| объявлено, не доказано | 1 | 1 |
| отвергнуто | 0 | 0 |
Переехало утверждение о настоящей функции корпуса:
| функция | откуда | утверждение | было | стало |
|---|---|---|---|---|
| «Сумма пары» | docs/examples/measure/natural.flang | «сумма пары неотрицательна» | сетка 1 значение (примеры функции) | доказано по объявленным типам аргументов: правило «неотрицательность по построению» |
«Было» здесь — это состояние С постусловием и БЕЗ правки ядра, измеренное прогоном ведомости (сетка 3 по корпусу), а не восстановленное по памяти: тем же способом, каким мерилось «было» у трёх утверждений индукции выше.
Тело перенесено дословно в flang/proof/examples/corpus-natural.flang и сличается машиной — тем же тестом, что три прежних corpus-*.flang. Причина переноса та же и не новая: docs/examples/measure входит в корпус побайтовой неподвижной точки самоприменения целиком, и слово обеспечивает рядом с той функцией сломало бы сверку разборщика.
Долг, который эта работа сделала нагруженным: вход, лгущий про тип. Факт «первое не меньше 0» верен ровно настолько, насколько его проверяет типизатор. На границе flang run --args он не проверяется вовсе, и теперь это не гигиена, а дыра под доказанным утверждением:
$ flang run docs/examples/measure/natural.flang --function «Сумма пары» \
--args '{"первое": -5, "второе": -5}'
{"function":"Сумма пары","args":{"первое":-5,"второе":-5},"result":-10}
$ … --args '{"первое": 2.5, "второе": 0.5}'
{"function":"Сумма пары","args":{"первое":2.5,"второе":0.5},"result":3}
Отрицательное и дробное объявлены неотрицательное и проходят молча; строка ловится, но не на границе, а внутри — на операции add, то есть по месту употребления, а не по объявлению. bindArguments в командной строке на JavaScript типов не читает ни одной строкой. Постусловие спасает только там, где оно написано: у flang/proof/examples/corpus-natural.flang тот же вход даёт честный отказ FLANG_PROPERTY, но это работа рантайма, а не проверка входа.
Чинится это НЕ в ядре: ядро обязано верить объявленному типу — иначе объявленный тип не значит ничего. Чинится на границе, сверкой --args с объявленными типами параметров.
Долг закрыт, и закрыт именно там, где назван: checkArguments сверяет вход с объявленным типом ДО вычисления (ветка work/entry-types-113, влита в сборку work/svodka вместе с этой). Ровно это и позволило отдать ядру вторую границу — см. следующий раздел.
И вторая граница того же объявления — потолок, с числами
Довод, по которому потолок вчера ядру НЕ отдали, стоял в flang/self/proof-kernel.flang дословно: «факт, которым никто не пользуется, — это доверие, за которое никто не отвечает». Отвечающий появился (раздел выше), и потолок отдан. Правил сведения стало три; устройство правила, его доказательство о IEEE-754 и главная ловушка — в разделе 3б-кватер.
Свод по корпусу до этой работы и после (flang/scripts/proof-ledger.mjs):
| было | стало | |
|---|---|---|
| файлов в корпусе | 163 | 164 |
| функций | 2800 | 2801 |
| тотальных | 2197 | 2198 |
| утверждений высказано | 10 | 11 |
| доказано ядром | 7 | 8 |
| из них индукцией по объявленной сумме | 6 | 6 |
| из них по объявленному типу аргумента | 1 | 2 |
| сетка | 2 | 2 |
| объявлено, не доказано | 1 | 1 |
| отвергнуто | 0 | 0 |
Утверждение здесь новое, а не переехавшее, и сетка потому не изменилась: сеткой оно не стояло никогда — до этой работы его нельзя было даже высказать так, чтобы ядро о нём что-нибудь сказало.
| функция | откуда | утверждение | было | стало |
|---|---|---|---|---|
| «Разность пары» | docs/examples/measure/natural.flang | «разность пары не выходит за точный потолок» | ядро молчит: у цели нет вида, к которому есть правило | доказано по объявленным типам аргументов: правило «ограниченность точным потолком по построению» |
«Было» здесь — состояние С постусловием и БЕЗ правки ядра, измеренное прогоном: диагностик 0, вердиктов ядра 0.
Тело перенесено дословно в flang/proof/examples/corpus-natural-ceiling.flang и сличается машиной — тем же тестом, что четыре прежних corpus-*.flang, и по той же причине: docs/examples/measure входит в неподвижную точку самоприменения целиком.
Долга эта работа не завела ни одного, и слов поверхности не прибавила ни одного. Утверждение записано восемью уже стоящими словами; число 2⁵³−1 написано в программе цифрами, потому что имён констант у языка нет, — но ядро сверяет цель не с этими цифрами, а с верхом точной сетки из flang/self/types.flang.
И третья граница той же подписи — ЦЕЛОСТЬ, с числами
Дно и потолок — это отрезок. Третье, что обещает точный тип и что ядро до сих пор не читало, — что значение ЦЕЛОЕ. Пока правил было три и все три про порядок, целость и правда была фактом, которым никто не пользуется. Появился случай, которому она нужна не для красоты, а иначе правило ЛОЖНО, — и она отдана.
Случай — ОСТАТОК, и он приехал вместе с точным десятичным типом (сотых, тысячных; docs/flang/SPEC.md, раздел 3). Деньги на число не считаются, и это вычисление: 0.1 плюс 0.2 даёт 0.30000000000000004, а (0.1 плюс 0.2) плюс 0.3 и 0.1 плюс (0.2 плюс 0.3) дают 0.6000000000000001 и 0.6 — одно выражение в разном порядке даёт два ответа. сотых — целое число сотых долей, и на нём сложение снова ассоциативно.
Правило добавлено в ДВА уже стоявших, а не четвёртым:
`а остаток от Л` не меньше 0 при неотрицательном КОНЕЧНОМ `а` и Л ≠ 0
`а остаток от Л` не больше Л−1 при неотрицательном ЦЕЛОМ `а` и целом Л > 0
Почему это теорема о IEEE-754, а не удобство. Остаток вычисляется ТОЧНО — fmod не округляет, это часть стандарта, — равен а − trunc(а/Л)·Л и повторяет знак делимого. Отсюда обе строки. Обе посылки несут вес, и каждая проверена изъятием, а не обещанием:
- целость.
5.5 остаток от 2равно 1.5, а 1.5 больше единицы. Сними факт целости — и вторая строка начнёт доказывать ложь о дробном входе; - конечность.
+∞ остаток от 100— это NaN, а NaN не больше и не меньше ничего. Поэтому в список «конечное целое» НЕ входят сложение и умножение: в IEEE-7541e308 плюс 1e308даёт+∞, и посылка стала бы ложной ровно на переполнении; - ненулевой делитель.
а остаток от 0— снова NaN, и «делитель не ноль» ядру взять неоткуда, кроме как из написанного числа. Имя типанеотрицательноенулём быть вправе, и потому именем делитель быть не может.
Свод по корпусу до этой работы и после (flang/scripts/proof-ledger.mjs):
| было | стало | |
|---|---|---|
| файлов в корпусе | 174 | 175 |
| функций | 3838 | 3847 |
| тотальных | 3065 | 3074 |
| утверждений высказано | 49 | 53 |
| доказано ядром | 29 | 32 |
| из них индукцией по начальной алгебре | 11 | 11 |
| из них без теоремы | 18 | 21 |
| из них факт дал объявленный тип аргумента | 3 | 6 |
| сетка | 19 | 20 |
| законов на сетке | 10 | 11 |
| объявлено, не доказано | 1 | 1 |
| отвергнуто | 0 | 0 |
| функция | откуда | утверждение | было | стало |
|---|---|---|---|---|
| «Копеек в остатке» | docs/examples/money/exact-decimal.flang | «копеек не меньше нуля» | функции не было; та же функция над число — сетка примеров | доказано по объявленным типам: правило «неотрицательность по построению» |
| «Копеек в остатке» | там же | «копеек меньше ста» | то же | доказано по объявленным типам: правило «ограниченность точным потолком по построению» |
| «Сложить копейки» | там же | «сумма копеек не меньше нуля» | то же | доказано по объявленным типам: правило «неотрицательность по построению» |
И ЗАКОН, которого у число НЕТ
Одиннадцатый закон корпуса — моноид «Копейки»: носитель сотых, операция «Сложить копейки», единица 0. Сетка 5 значений, 125 троек, нарушений не найдено.
Тот же моноид, объявленный на число с долями рубля в сетке, не собирается:
FLANG_MONOID_ASSOC: моноид «Доли»: операция не ассоциативна:
на 0.1, 0.1, 0.6 слева вышло 0.8, справа 0.7999999999999999
Это самое важное, что даёт точный тип сверх точности, и оно не рассказано, а ПРОВЕРЕНО: язык отличает тип, на котором моноид есть, от типа, на котором его нет, и отличает ОТКАЗОМ СБОРКИ. Обе половины стоят тестом (проверка точных десятичных, «ЗАКОН: …»).
Что при этом остаётся сеткой и названо своим словом: ассоциативность и нейтральность проверены на 125 тройках, а не доказаны — правил о равенстве вычислений у ядра нет и быть не может. КОММУТАТИВНОСТИ язык не проверяет вовсе: её нет в объявлении моноида, и говорить, что она доказуема, было бы неправдой.
Дефект, вскрытый этой работой и починенный в ней же. носитель неотрицательное на совершенно исправной программе давал «носитель объявлен как неизвестный тип» — носитель брался из AST сырым и не проходил нормализацию, а имена уточнений (в отличие от число) ключевыми словами не являются и приезжают узлом named. Значит НИ ОДИН моноид над неотрицательное, целое, сотых или тысячных не собирался никогда. Чинится одной строкой — тем же normalizeType, каким читаются типы параметров.
Изъятие здесь чище некуда: тело то же самое, отличается ОДНО СЛОВО в подписи — сотых против число, — и слово «доказано» исчезает (проверка точных десятичных, «ИЗЪЯТИЕ: та же функция над число теряет слово «доказано»»).
Аксиом по-прежнему ноль, слов поверхности не прибавилось ни одного. Охват ядра вырос ровно на одну функцию корпуса — она восемнадцатая в списке проверки утверждений набора и первая, пришедшая не от неотрицательное.
Долг, названный прямо, — и закрытый 15 августа 2026
Здесь стояло: у восьми слов нет продукции в самоприменённом парсере (flang/self/parser.flang). Продукция написана — все восемь разбираются четырьмя продукциями, AST сходится со свидетелем побайтово, и flang/proof целиком входит теперь в корпус сверки разборщика. Запись оставлена не для памяти, а потому что ниже названа ЦЕНА этого долга, и цена — часть измерения.
Побайтовая неподвижная точка самоприменения не страдает и теперь, но говорится это уже о другом: в её корпусе (flang/stdlib, flang/core, docs/examples/leetcode, docs/examples/measure, flang/self) стоят СЕМЬ постусловий, и AST на них сходится со свидетелем побайтово — это прогнано, а не предположено (сверка разборщиков). Теорем там по-прежнему ноль, и это не забывчивость: теорему в этих каталогах никто со свидетелем не сверял, а flang/proof в корпусе сверки стоит целиком. Счёт сторожит проверка ядра («корпус самоприменения употребляет ровно обеспечивает и ровно семь раз»), и краснеет он в обе стороны.
Этот долг оказался дороже, чем читался, и работа по индукции в него уперлась. Пока его не было видно, он читался как «доказательства просто лежат в другом каталоге». На деле он значит другое: ни одно утверждение о функции корпуса нельзя записать РЯДОМ С ФУНКЦИЕЙ, потому что библиотека и решённые задачи входят в неподвижную точку целиком. Три доказанных индукцией утверждения корпуса поэтому лежат в flang/proof/examples/corpus-*.flang с дословно перенесёнными телами, а не у самих функций. Проверено падением: написанные в библиотеке, они покраснили сверку разборщиков на побайтовой сверке AST.
Цена долга измерена и называется прямо: продукция в самоприменённом парсере — не гигиена, а то, что отделяло слой доказательства от корпуса. Пока её не было, каждое новое утверждение о библиотеке требовало переноса тела, а перенос надо сличать машиной, чтобы «доказано» не оказалось про похожую функцию. Долг оплачен 15 августа 2026, и оплата видна числом: семь утверждений о настоящих функциях языка стоят теперь у самих функций, переносить и сличать больше нечего.
Для ПОСТУСЛОВИЯ это верно уже сегодня. Для ТЕОРЕМЫ — ещё нет, и разница здесь не в парсере (он разбирает и её), а в сверке: доказательств индукцией в каталогах неподвижной точки не стояло ни одного, значит и сверено там ничего не было. Пять corpus-*.flang с индукцией поэтому остаются на месте до отдельной работы, которая перенесёт туда теорему и прогонит побайтовую сверку целиком.
У пятого (corpus-factorial.flang) причина ДРУГАЯ, и её надо назвать отдельно. Его функция лежит не в корпусе неподвижной точки, а в docs/examples/rosetta/factorial.flang — образце сверки ЧЕТЫРЁХ ПОВЕРХНОСТЕЙ (проверка поверхностей). Восьми слов доказательства у эсперантской и китайской поверхностей нет вовсе, поэтому утверждение, написанное по-русски и не написанное там, разломало бы сверку деревьев, которая и есть смысл тех файлов. Двойник той же функции в docs/examples/measure/natural.flang упирается уже в первую причину — неподвижную точку. То есть у одного и того же тела ДВА разных запрета, и оба сняты будут разными работами.
Закрыто: выражение постусловия типизируется
Здесь стоял долг, найденный этой же работой: типизатор на JavaScript не читал поле postconditions ни одной строкой, и обеспечивает «бессмыслица» результат плюс "строка" проходило flang check с ответом valid: true. Долг закрыт — проверка постусловий в типизаторе на JavaScript, — и та же программа отвечает теперь двумя диагностиками: правый операнд «add»: ожидался число, получен строка и постусловие «бессмыслица» даёт число, а постусловие обязано быть признаком.
Проверок две, и вторая не следует из первой: результат плюс 1 типизируется безупречно и не является утверждением ни о чём. Область видимости взята у вычислителя, а не выдумана: результат (точнее, bind постусловия) кладётся поверх параметров ровно так же, как это делает вычислитель. Имя, связанное квантором для всех, тип получает из параметра — другого источника у него нет и быть не может.
Запись убрана по правилу из проверки недостач: у названной недостачи стоит программа-улика, и как только недостачи не стало, улика покраснела и потребовала убрать запись. Так и вышло.
Закрыто: дано сверяется по типу, постусловие отвечает за своё завершение
Здесь стояли два долга той же работы. Оба закрыты, и оба — программой, которая раньше проходила молча.
Переменная теоремы сверяется по ТИПУ, а не только по имени. дано свет: «Несуществующий» отвечало valid: true; теперь та же программа отвечает FLANG_PROOF_VAR_TYPE: теорема «штраф неотрицателен» вводит «свет» как «Несуществующий», а у функции «Штраф» этот параметр объявлен как «Светофор».
Закрыто это НЕ в типизаторе, и место стоит назвать. Типизатор узел theorem не читает ни одной строкой — теорему с функцией сводят обязательства, по имени, и там же стояла ровно половина этой сверки: «у функции такого параметра нет». Вторая половина — тип — легла рядом с первой, потому что это одно сведение, а не два. Парсер строит переменную теоремы тем же parseParameter, что и параметр функции, и говорит об этом прямо: «переменная теоремы и параметр функции — одно и то же: имя с типом».
Сверка идёт с ПАРАМЕТРОМ, а не с объявлениями модуля, и это строго сильнее: дано свет: число при параметре свет: «Светофор» называет существующий тип и всё равно доказывает не о той функции. Тип параметра типизатор уже проверил на существование — совпасть с ним значит и существовать, и относиться к делу.
Постусловие вошло в анализ завершаемости. Анализ на JavaScript поле postconditions не читал вовсе, и программа ниже отвечала valid: true:
функция «Вечная»
принимает н: число
возвращает признак
если н не меньше 0 то «Вечная» от (н плюс 1) иначе да
тотальная функция «Ф»
принимает н: число
возвращает число
обеспечивает «зовёт обычную» «Вечная» от результат
н
Теперь она отвечает FLANG_NOT_TOTAL: тотальная функция «Ф» вызывает обычную функцию «Вечная» в постусловии «зовёт обычную»: обычная функция может не завершиться … Постусловие считается после каждого возврата, значит вызов из него — часть вызова «Ф», а не заметка рядом с ним. Место названо в САМОМ сообщении не для полноты: обеспечивает стоит НАД телом и читается как объявление, и автор, услышавший «вызывает обычную», пошёл бы искать вызов в тело и не нашёл бы его. На вызове из тела обе вставки пусты, и старые сообщения остались прежними знак в знак.
Правило взято то же, каким проверяется тело, и легло туда же, где уже обходится мера, — по тому же доводу, каким там обходится она: обещание, которое ВЫЧИСЛЯЕТСЯ, отвечает за своё вычисление наравне с телом. Область видимости взята у вычислителя, а не выдумана: bind кладётся поверх параметров так же, как stepPost кладёт результат поверх окружения вызова, а происхождение у него null — про только что построенное значение не известно ничего.
Что при этом НЕ сломалось, и это измерено, а не обещано. Постусловие, зовущее ТОТАЛЬНУЮ функцию, остаётся чистым. Рекурсия по части значения из постусловия («Длина» от (хвост элементы)) доказывается структурно — она и вправду обрывается. А «Ф» от (н минус 1) из постусловия отвергается: под обеспечивает нет той ветви если, которая давала бы «н» дно, и без дна цепочка не обрывается — базовый случай считает своё постусловие тоже.
Изъятия показаны тестами: проверка завершаемости («постусловие — тоже код…» и «постусловие обходится в области видимости вычислителя…») и проверка ядра («„дано“ теоремы сверяется по ТИПУ…»).
~~Долг, оставшийся от той же работы: гипотеза дано не типизируется~~ — ЗАКРЫТ 16 августа 2026
Закрыт на неслитой ветке
work/tipizirovat-dano(5b68cbf), в этом дереве ещё стоит. ТамcheckTypesполучил последним проходомcheckTheorems(свидетель) и«Проверить теоремы»(эталон,flang/self/types.flang): гипотеза читается общим выводом типа в области видимости параметров ФУНКЦИИ, чьё постусловие теорема закрывает, и тип её обязан бытьпризнак. Все три улики ниже там отвергнуты, корпус не сдвинулся (гипотез-утверждений в дереве 0 при 75 теоремах), аксиом по-прежнему ноль. Здесь всё написанное дальше — правда; сольётся ветка — раздел снимать целиком.
Закрыта одна форма слова дано — переменная дано <имя>: <тип>. Вторая форма, гипотеза дано <утверждение>, не типизировалась НИ ОДНОЙ СТРОКОЙ: слова theorems в типизаторе на JavaScript было ноль вхождений. Улика — три программы, и все три flang check принимал:
теорема «штраф неотрицателен»
дано свет: «Светофор»
дано свет плюс "строка" → valid: true, диагностик 0
дано 5 → valid: true, диагностик 0
дано "строка" → valid: true, диагностик 0
Гипотеза ехала в обязательство полем hypotheses и доходила до ядра невычислимой: сложение варианта со строкой не даёт признака никогда, то есть допущение не является допущением. Ловилось у гипотезы только несвязанное имя, и ловил его разбор языка: область видимости проверялась, тип — ничем.
Чинится тем же способом, каким чинилось постусловие, и место названо было верно: обязательства типов не читают и читать не должны, значит это типизатор на JavaScript. Проход checkTheorems стоял последним в checkTypes, читает гипотезу общим inferExpr и требует от неё признак; отказ — тот же FLANG_TYPE, что у постусловия и предусловия. Ответ на те же три программы теперь: «теорема «штраф неотрицателен»: гипотеза даёт неотрицательное (строка, число), а гипотеза обязана быть признаком».
Среда — параметры ФУНКЦИИ, а не объявления самой теоремы. Это среда, в которой гипотеза действительно окажется: обязательство едет к ядру с vars = параметры функции. Читать объявления теоремы вторым источником значило бы получить два ответа на вопрос «какого типа здесь имя» и второе сообщение о том же изъяне — тип переменной теоремы сверяют с параметром обязательства (FLANG_PROOF_VAR_TYPE), и сверяет строго. Связка результат кладётся поверх параметров тем же способом, что в постусловии: разбор языка держит это имя в области видимости всей теоремы, и отказать ему типизатором значило бы отвергнуть принятое разбором.
Обе стороны сделаны в один заход. Эталон flang/self/types.flang получил тот же проход («Проверить теоремы»), и сверка у него побайтовая: тот же код, тот же текст, то же место. Стережётся это СВОЕЙ программой, а не корпусом, и причина измерена: теорем в дифференциальном корпусе ноль — они живут в flang/proof, а тот в сверку не входит, — значит прохода у эталона могло бы не быть вовсе, не покрасив ни одной проверки (сверка типизаторов, «гипотеза теоремы»). Изъятие проведено с обеих сторон порознь, и краснеет каждая своя половина.
Корпуса это не сдвинуло, и число тому есть: гипотез-утверждений в корпусе 0 при 75 теоремах и 26 записях дано имя: тип. Ведомость до и после одна — 152 высказано, 152 доказано ядром, 0 отвергнуто. Дыра была открыта, но ещё не эксплуатировалась; закрыта она до того, а не после.
Замер по корпусу: сколько дало ядру чтение объявленного типа
Работа, названная выше, отвечала на вопрос «умеет ли ядро». Следом задан второй, и он был не задан ни разу: сколько корпуса стало от этого доказуемым. Отвечать такое памятью нельзя, поэтому вопрос ИСПОЛНЕН — обходом всех 2800 функций корпуса, каждой подставили её тело на место результат и спросили у свести, сводится ли «результат не меньше 0». Обход стоит в проверка утверждений набора и краснеет, если ответ изменится.
Ответ: десять функций из 2800, и семь из них ПИСАТЬ БЫЛО НЕЛЬЗЯ. Не потому, что ядро их не берёт — берёт, — а потому, что они лежат в каталогах побайтовой неподвижной точки самоприменения: flang/self — пять, docs/examples/leetcode — одна, docs/examples/measure — одна. Там у слова обеспечивает не было продукции у парсера на flang. Это не граница ядра, это долг № 2 ниже, и он был измерен: он стоил семи утверждений.
Раскладка эта — не из головы, она печатается тем же обходом и сверяется в проверка утверждений набора («охват: семь из десяти НАПИСАНЫ»): пять, одна и одна, а не «шесть и одна», как стояло в первом счёте этой работы, — там docs/examples/measure выпал, а flang/self вырос на его место.
Написаны поэтому сперва два, оба на настоящих функциях корпуса и на двух поверхностях одной и той же: «Значение цифры» из docs/examples/rosetta/roman-numerals.flang и «Value of a symbol» из …-english.flang.
| было | стало | |
|---|---|---|
| утверждений высказано | 10 | 12 |
| доказано ядром | 7 | 9 |
| из них индукцией по объявленной сумме | 6 | 6 |
| из них без теоремы (цель сведена с телом) | 1 | 3 |
| из них факт дал объявленный тип аргумента | 1 | 1 |
| сетка | 2 | 2 |
| объявлено, не доказано | 1 | 1 |
| отвергнуто | 0 | 0 |
Последняя строка тут — та самая улика, из-за которой строкой выше разведены два слова: утверждений без теоремы стало втрое больше, а объявленный тип как читался одним, так одним и остался.
Долг закрыт: семь отложенных утверждений написаны (15 августа 2026)
Продукция слоя доказательства в flang/self/parser.flang написана — и семь утверждений, названных выше поимённо, переехали К САМИМ ФУНКЦИЯМ. Копий больше не нужно: тело не переносится, сличать нечего, «доказано» относится к той самой функции по построению, а не по сверке двух деревьев разбора.
| функция | откуда | утверждение |
|---|---|---|
| «Значение цифры» | docs/examples/leetcode/013-roman-to-integer.flang | «значение цифры неотрицательно» |
| «Сумма пары» | docs/examples/measure/natural.flang | «сумма пары неотрицательна» |
| «Байтов у символа» | flang/self/emit-c.flang | «байтов у символа неотрицательно» |
| «Наименьшая основа» | flang/self/parser.flang | «наименьшая основа неотрицательна» |
| «Размер пачки» | flang/self/parser.flang | «размер пачки неотрицателен» |
| «Версия ведомости» | flang/self/proof.flang | «версия ведомости неотрицательна» |
| «Потолок точных» | flang/self/types.flang | «потолок точных неотрицателен» |
При сборке work/svodka к ним прибавились пять того же вида — из трёх влитых с origin слоёв эталона:
| функция | файл | утверждение |
|---|---|---|
| «Предел сетки изоморфизма» | flang/self/iso.flang | «предел сетки изоморфизма неотрицателен» |
| «Предел сетки моноида» | flang/self/monoid.flang | «предел сетки моноида неотрицателен» |
| «Предел сетки монады» | flang/self/monad.flang | «предел сетки монады неотрицателен» |
| «Предел стрелок» | flang/self/monad.flang | «предел стрелок неотрицателен» |
| «Предел сетки множеств» | flang/self/sets.flang | «предел сетки множеств неотрицателен» |
| было | стало | |
|---|---|---|
| утверждений высказано | 12 | 19 |
| доказано ядром | 9 | 16 |
| из них индукцией по объявленной сумме | 6 | 6 |
| из них без теоремы (цель сведена с телом) | 3 | 10 |
| из них факт дал объявленный тип аргумента | 1 | 2 |
| сетка | 2 | 2 |
| объявлено, не доказано | 1 | 1 |
| отвергнуто | 0 | 0 |
Сетка и «объявлено, не доказано» не выросли ни на единицу, и это не совпадение: ни одно утверждение не переехало между строками, все семь добавлены новыми.
Свод СОБРАННОГО дерева (work/svodka, 15 августа 2026)
Таблицы выше — шаги отдельных веток, и «стало» в каждой означает «стало на той ветке». Собранное дерево считалось один раз и одной командой (node flang/scripts/proof-ledger.mjs); вот что она напечатала.
| main | собрано | |
|---|---|---|
| файлов в корпусе | 162 | 169 |
| функций | 2799 | 3329 |
| тотальных | 2196 | 2678 |
| сторожей (мест) | 100 | 100 |
| утверждений высказано | 9 | 25 |
| доказано ядром | 6 | 22 |
| из них индукцией по объявленной сумме | 6 | 6 |
| из них без теоремы (цель сведена с телом) | — | 16 |
| из них факт дал объявленный тип аргумента | — | 3 |
| сетка | 2 | 2 |
| объявлено, не доказано | 1 | 1 |
| отвергнуто | 0 | 0 |
Шесть утверждений сверх девятнадцати ветки пришли не работой над ядром: одно — «разность пары не выходит за точный потолок» из work/proof-ceiling, пять — пределы сеток трёх влитых с origin слоёв эталона (self/iso.flang, self/monoid.flang, self/monad.flang — два, self/sets.flang). Ядро берёт их по одной и той же причине: предел объявлен литералом. Сетка и «объявлено, не доказано» не тронуты и здесь.
Охват при этом остался тем же по существу и вырос по счёту: цель «результат не меньше 0» сводится у 15 функций из 3329, и все двенадцать, лежащие в корпусе неподвижной точки, теперь НАПИСАНЫ (проверка утверждений набора).
Цена перехода оказалась не нулевой, и назвать её надо. Постусловие в корпусе неподвижной точки сломало одну сверку — и сломало по делу: flang/self/types.flang не типизировал постусловие ВОВСЕ, а свидетель типизирует (checkPostconditions). На чистом файле это не видно никогда — обе стороны говорят «диагностик нет». Видно стало на ПОРЧЕНОМ: у 013-roman-to-integer.flang число заменяется строкой, свидетель говорит «левый операнд сравнения «gte» имеет тип строка», эталон молчал. Дыра закрыта пятью функциями в self/types.flang; подробности — docs/flang/self/SPEC.md, «Постусловие типизируется с 15 августа 2026».
Чего этот переезд НЕ отменил. Четыре файла flang/proof/examples/corpus-*.flang остаются на месте, и три из них — по-прежнему единственный способ высказать своё: теорема с индукцией в корпус неподвижной точки не переносилась и со свидетелем там побайтово не сверялась. Четвёртый (corpus-natural.flang) стал дословной копией утверждения, которое теперь стоит и у функции, — отсюда «факт дал объявленный тип» читается ДВАЖДЫ, а не один раз. Убирать копию — отдельная работа: на неё смотрит проверка ядра четырьмя проверками.
Узкое место названо числом, а не настроением. В корпусе нет НИ ОДНОГО поля варианта, объявленного неотрицательное, — значит declarations у посылок индукции, ради которых правился flang/self/proof-initial.flang, сегодня не применяются к корпусу нигде. А неотрицательное-параметры стоят у функций, чьи тела ядру не по зубам: умножение («Факториал»), вызов («Фибоначчи»), длина и обращение к полю. Шесть таких случаев — правдивых утверждений, которых ядро не берёт, — стояли исполняемым списком в том же файле теста: у каждого проверяется и правда (на примерах автора), и отказ ядра (дословно, вхождением в why). Сегодня их пять: «Глубина дерева» вычеркнута развёрткой определения (раздел «И третье»), и причина у «Фибоначчи» переписана — «развёртки определений у ядра нет» стало неправдой, а строка осталась, потому что рекурсия там идёт по числу.
И у самого списка нашёлся дефект — тем, что он не сработал. Ядро научилось умножению, а строка «Факториал», просившая ровно умножения, осталась зелёной: проверка требовала, чтобы названная в причине конструкция СТОЯЛА в теле, и не требовала, чтобы она была помехой ЕДИНСТВЕННОЙ. У «Факториала» помех две, и названа была не решающая: умножается н не на неотрицательное, а на непрозрачный вызов себя же. Это тот же дефект, что у «Глубины дерева» («вопрос и причина разошлись»), с другой стороны.
Починка исполняемая и стоит рядом со списком: из настоящего тела корпусной функции убирается РОВНО названная помеха, всё прочее остаётся знак в знак, и у ядра спрашивается ещё раз. «Факториал» от (н минус 1) заменяется на н — цель сводится, значит помеха не умножение. Снять из ядра случай mul — эта проверка краснеет первой.
Переписаны обе причины, и вычеркнуть по-прежнему нечего: оба утверждения правда, и оба ядру не по зубам. У «Факториала» мешает непрозрачный вызов (тот же класс, что «Фибоначчи»); у «Пропущено ячеек» старая причина называла две помехи разом, и вторая («поле объявлено число») ядру неподвластна вовсе — настоящая же помеха третья и прежде не называлась ни разу: итог.пропущено есть поле свёртки, и после нормализации на месте итог стоит свёртка, а не имя.
Почему обращение к полю записи фактом ПОКА не стало — и почему теперь это выбор, а не запрет. Заказ просил читать объявленный тип поля так же, как ядро читает тип аргумента и тип поля варианта. Мешало этому не flang/self/proof-kernel.flang, а то, что за неотрицательное в поле ЗАПИСИ не отвечал никто: recordType в типизатор на JavaScript сверял тип поля через sameType — симметричное равенство ФОРМЫ, для которого неотрицательное, целое и число суть один и тот же IEEE-754 double, — тогда как соседнее место для поля ВАРИАНТА давно сверялось через годится. Список из десяти мест, переведённых на годится, назвал поле варианта и пропустил поле записи.
Дыра была измерена и ЗАКРЫТА. Измерение: запись «Ящик» с вес равным (0 минус 1) при вес: неотрицательное проходило без единой диагностики, и поле ОТМЫВАЛО значение — «Прямо» от ((запись «Ящик» с вес равным (0 минус 1)).вес) типизировалось, хотя «Прямо» принимает неотрицательное. Тем самым бился факт, который у ядра УЖЕ есть: постусловие стояло «доказано по объявленным типам аргументов… обо ВСЕХ входах», а прогон отказывал FLANG_PROPERTY. То есть дыра позволяла доказать ложь.
Правка — одно слово (sameType → годится в recordType), и цена её измерена на всём дереве, а не оценена: 183 файла .flang (stdlib, examples, core, self, proof, conc) дают от неё ровно ОДНУ диагностику, в flang/self/monoid.flang, где «номер»: неотрицательное принимал («сбор».«номер») плюс 1, а сумма двух неотрицательное натуральным быть не обязана. Починено объявлением поля («номер»: число), а не ядром: объявление теперь говорит то, что есть на деле.
Факта из поля записи у ядра по-прежнему нет, но причина сменилась с «нельзя» на «ещё не заведён»: отвечающий появился, и завести факт можно источником по образцу известноеПоТипу. Улика исполняемая и стоит на месте, только в положительной форме (проверка ядра, «ДЫРА ЗАКРЫТА: поле записи больше не отмывает значение»): она требует диагностики там, где раньше требовала её отсутствия, и краснеет, если факт заведут молча.
Из этого списка два случая — НЕ про правило сведения, и потому названы отдельно здесь. Оба про то, чем закрывается посылка.
И третье: развёртка определения, с числами
Свод по корпусу до этой работы и после (flang/scripts/proof-ledger.mjs):
| было | стало | |
|---|---|---|
| функций в корпусе | 2800 | 2802 |
| утверждений высказано | 12 | 13 |
| доказано ядром | 9 | 11 |
| из них индукцией по объявленной сумме | 6 | 8 |
| из них без теоремы (цель сведена с телом) | 3 | 3 |
| из них факт дал объявленный тип аргумента | 1 | 1 |
| сетка | 2 | 1 |
| объявлено, не доказано | 1 | 1 |
| отвергнуто | 0 | 0 |
| шагов в принятых термах | 14 | 18 |
Переехали два утверждения, и оба были названы уликами ЗАРАНЕЕ — одно владельцем языка, другое исполняемым заказом в проверке утверждений набора:
| утверждение | где | было | стало |
|---|---|---|---|
| «копия равна оригиналу» | flang/proof/examples/stack.flang | сетка 1 значение (примеры функции) | доказано индукцией по «Стопка», правило «тождество после переписки допущением» |
| «глубина дерева неотрицательна» | flang/stdlib/tree.flang → corpus-tree-depth.flang | утверждения не было: написать его было нечем | доказано индукцией по «Дерево», правило «неотрицательность по построению» |
Второе — утверждение о настоящей функции корпуса, и «было» у него означает не «сетку», а пустоту: заказ прямо говорил, что ядро этого не берёт, и потому утверждение не писалось. Тела «Глубина дерева» и «Глубже» перенесены дословно, и сличаются машиной ОБА: тело «Глубже» вошло в доказательство развёрткой, значит расхождение с библиотекой означало бы доказательство о другой функции ровно так же, как расхождение в теле доказываемой.
Охват «результат не меньше 0» не изменился: как было десять функций из 2800, так и осталось десять из 2802. Это не разочарование, а измерение, и оно названо: развёртка помогает там, где утверждение доказывается ШАГОМ ИНДУКЦИИ, а обход охвата спрашивает ядро про постусловие БЕЗ теоремы — одной подстановкой тела. Развёртка при этом в обходе включена (иначе он мерил бы вчерашнее ядро), и у одной из десяти функций («Байтов у символа» в flang/self/emit-c.flang) она теперь и правда срабатывает — просто эта функция бралась и без неё.
Из-за этого же одна строка заказа была вычеркнута ЧЕЛОВЕКОМ, а не тестом, и об этом сказано в самом файле теста. Причина строки «Глубина дерева» была про шаг индукции, а проверка при ней — про постусловие без теоремы; вопрос и причина разошлись, и потому строка осталась бы зелёной после того, как ядро научилось. Список, который не умеет вычеркнуть себя сам, — это половина списка, и исправляется это не подновлением строки, а проверкой посылки принципа вместо тела функции.
Закрыто: базу посылки было нечем закрыть, кроме примера
Здесь стоял долг, и запись его была такой: «Глубина» из docs/examples/leetcode/104-maximum-depth-of-binary-tree.flang (и её эталон из 110-balanced-binary-tree.flang) доказывалась бы индукцией целиком — обе посылки СВОДЯТСЯ, — но написать теорему нельзя, потому что базу закрывать нечем: по предположению в базе отвергается по делу, а по примеру требует примера, которого у этой функции нет ни одного. Цена была измерена: две функции корпуса.
Долг закрыт ровно тем шагом, каким и был назван, и без нового слова поверхности. Покрытие означает теперь то, чем было с самого начала: о КАЖДОЙ посылке принципа сказано. Сказать может автор словом случай или ЯДРО — одним свести на посылку, без единого перебора, тем же путём, каким закрывается постусловие без теоремы (поОбъявленномуТипу).
Граница у правила одна, зато жёсткая: ядро закрывает посылку БЕЗ ЕДИНОГО ДОПУЩЕНИЯ ИНДУКЦИИ. Допущения в этот вызов свести не передаются вовсе — только объявления подписи и факты самого разбора. Довод не про осторожность, а про то, что такое доказательство индукцией: посылка, сведённая без допущений, индукции не потребовала (она того же рода, что постусловие без теоремы); посылка, сведённая ДОПУЩЕНИЕМ, — это шаг, и что он опёрся на допущение, обязан сказать автор словом по предположению. Разреши здесь допущения — теорема из одного случая доказывала бы всё подряд, а ведомость писала бы «шаг при допущении» о шаге, которого никто не писал. Проверено изъятием обеих сторон (проверка ядра: «изъят ШАГ индукции» краснеет, «изъята БАЗА» зеленеет со словом by: reduction).
Что от этого стало в дереве. Из corpus-depth.flang УБРАН пример «У листа глубины нет» — единственная строка, которой в оригинале не было и которая дописывалась ради базы. Перенос стал дословным, и дословность сличается теперь и по примерам, а не только по телу с подписью. Обе функции корпуса, в которых цена долга была измерена, проверяются исполняемо: к тексту 104-… и 110-… дописываются РОВНО ДВЕ вещи — утверждение и теорема, — и ядро говорит «доказано индукцией по «Дерево»». На ядре до этой работы тот же файл отвергался кодом FLANG_PROOF_INDUCTION_CASES.
И вычеркнул строку заказа ТЕСТ, а не человек — в отличие от предыдущей вычеркнутой строки. Причина строки была про ПОСЫЛКУ ПРИНЦИПА, а проверка при ней спрашивала про постусловие БЕЗ теоремы; вопрос переписан на тот, про который причина.
Чего это НЕ закрыло. Тот же тупик с другой стороны — у варианта БЕЗ рекурсии, но С полями (вариант «Ответ» содержит код: число, тело: строка в docs/examples/web/orders-api.flang) — остался: посылка не замкнута, значит по примеру её не закроет; допущений у неё нет, значит и по предположению тоже; а свести её не закрывает, потому что в заключении живёт свободное имя поля. Закроет её упрощатель, как и было сказано.
Закрыто: «нарушений не найдено» стало означать «искали и не нашли»
Долг был записан здесь с уликой: ведомость печатала у сетки строку «сетка N значений (примеры функции): нарушений не найдено», а число N брала из flang/self/obligations.flang (grid: примеры.length) — то есть СЧИТАЛА примеры, а не прогоняла их. Порча на один знак (не меньше 0 → не меньше 1) давала два ответа об одном файле в одну секунду:
$ flang check docs/examples/rosetta/roman-numerals.flang --proof
постусловие «значение цифры неотрицательно» … — сетка 3 значения
(примеры функции): нарушений не найдено
$ flang test docs/examples/rosetta/roman-numerals.flang
{"valid":false, … "example":"Не римская цифра — ноль","code":"FLANG_PROPERTY"}
Выбран был ПЕРВЫЙ путь из двух названных — сетка считается прогоном, — потому что второй («сказать, кто ищет, а кто считает») оставлял бы читателю число, которого ни один слой не проверил. Цена измерена и оказалась в шуме: обход всего корпуса node flang/scripts/proof-ledger.mjs — 2,54 с до и 2,51 с после, потому что сеток в корпусе двенадцать и в каждой не больше трёх примеров. Общая цена ограничена сверху ценой flang test для тех же функций: прогоняются только примеры функций, при которых написано постусловие.
Ищет flang/self/grid.flang. Прогон идёт по программе-ТЕНИ, где оставлено ровно одно постусловие — то, о котором спрашивают, — и тогда отказ с его кодом принадлежит ему и никому больше; проверяются при этом ВСЕ возвраты функции, включая рекурсивные, потому что постусловие проверяет вычислитель, а не сам поиск. Пропустить то, что найдёт flang test, этот поиск поэтому не может.
Ответов три, и они не сливаются: «нарушений не найдено (искали прогоном на всех N)», «НАРУШЕНО на примере «…»» и «нарушений НЕ ИСКАЛИ». Третий сказан теми же словами нарочно: честное «не искали» допустимо, молчаливое «считаем, что всё хорошо» — нет.
Та же мерка, приложенная к соседним строкам свода
Строк в своде, говорящих «столько-то и ни одного больше», четыре. Проверены все четыре одним вопросом: получено ли число ПОИСКОМ.
| строка | было | стало |
|---|---|---|
| сетка примеров | считала примеры | прогоняет их (выше) |
| законы на сетке | checked заполнялся и при НАРУШЕННОМ законе | нарушения считаются, вердикт violated |
| законы на сетке | обход обрывался МОЛЧА | обрыв назван: «нарушений не найдено там, докуда дошли» |
| отвергнуто ядром | тавтология: отказной файл в свод не входит | считается и у файлов без ведомости |
| на веру | честна была и есть | — |
Закон, нарушенный на сетке, попадал в отчёт строкой честного. flang/self/monoid.flang, flang/self/monad.flang и flang/self/iso.flang клали запись в checked безусловно, и ведомость печатала о моноиде с двумя диагностиками «сетка 5 значений (предел 12), 2 троек: нарушений не найдено». Программа при этом отказная и flang check ведомости не печатает — но proofLedger вывозится как функция, и отчёт по ней собирают инструменты. Списано устройство с flang/self/sets.flang, который нарушенное вложение в checked не клал с самого начала.
Хуже того — обрыв обхода, и он достижим у ЦЕЛОЙ программы. В flang/self/monoid.flang стоял голый break авария: операция отказала на тройке — обход прекращался без единой диагностики, а запись уходила такая же, как у пройденной насквозь сетки. Замер на фикстуре (проверка отчёта о доказательствах, «оборванный обход сетки назван обрывом»): сетка 5 значений, обход оборвался на 11-й тройке из 125, а строка говорила «10 троек: нарушений не найдено». Это единственный из четырёх случаев, где соврать можно было прямо в выводе flang check --proof.
«Отвергнуто ядром: 0» было тавтологией. Отвергнутый терм — это отказ, у отказного файла ведомости нет, а свод складывал только ведомости: ноль означал «сюда такое не попадает», а читался как «искали и не нашли». Считается теперь и у файлов без ведомости; изъятие: испорченная теорема в corpus-numtree.flang даёт «отвергнуто ядром: 1» вместо прежнего нуля при тихо выпавшем файле.
Заказ от библиотеки: что меряет, чего не хватает
Здесь не пожелания, а замер по flang/stdlib — двадцать модулей, 12146 строк, 185 функций. Замер стоит исполняемым тестом (проверка утверждений библиотеки), а каждый пункт заказа — настоящей функцией библиотеки, настоящей попыткой доказательства и настоящим кодом отказа. Появится названное — проверка ПОКРАСНЕЕТ и потребует перевести утверждение из сетки в доказанное.
Скольких функций индукция может коснуться сегодня: 55 из 208. Было 28. Чтобы ядро могло хотя бы начать, нужны две вещи, и обе читаются с объявлений: параметр, у типа которого ЕСТЬ начальная алгебра, и тело — разбор этого параметра на верхнем уровне. Параметр объявленной суммы есть у 35, разбор по нему — у 28: это число не сдвинулось и сдвинуться не должно было. Параметр-СПИСОК есть у 90, разбор по нему — у 27, и весь прирост оттуда.
| модуль | функций | доступно было | доступно стало |
|---|---|---|---|
lists.flang | 28 | 0 | 11 |
higher-order.flang | 34 | 0 | 6 |
hashmap.flang | 24 | 8 | 8 |
tree.flang | 13 | 9 | 9 |
optional.flang | 11 | 6 | 6 |
strlists.flang | 12 | — | 5 |
strings.flang | 30 | 0 | 1 |
result.flang | 11 | 3 | 3 |
numbers.flang / sets.flang / logic.flang | 14 / 9 / 7 | 0 | 0 |
У числа и признака конструкторов нет вовсе, множество — своя работа, а strings.flang получил ровно одну функцию, и ту за работу над списком, а не над строкой: строке алгебра не дана нарочно (раздел 3а-бис).
| заказ | улика (настоящая функция) | отказ |
|---|---|---|
| ~~начальная алгебра встроенного списка~~ | ЗАКРЫТО. «Длина», «Индекс» (lists.flang), «Позиция где» (higher-order.flang) — доказаны индукцией по списку | — |
| ~~развёртка определений функций~~ | ЗАКРЫТО ДО ЭТОГО ЗАМЕРА веткой work/kernel-unfold (раздел 3б-бис): «Глубина дерева» (tree.flang) доказана — плоское определение «Глубже» разворачивается, и с ним же закрылась «Четвёрка» у «Глубины словаря» | — |
| связанное имя непрозрачного поля В ЗАКЛЮЧЕНИИ | «Глубина словаря» (hashmap.flang), когда ветвь возвращает спуск | тот же код: примером не закрыть — имя свободно, допущения нет |
| цель, сравнивающая не с нулём | «размер не меньше глубины» (tree.flang) — верно и отвергнуто; четыре утверждения о lists.flang | тот же код: «правил три — „не меньше 0“, „не больше конечного литерала“ и „равно“» |
| ~~заключение посылки для тела-свёртки~~ | ЗАКРЫТО НАПОЛОВИНУ 16 августа 2026 (раздел 3б-секстэ): цель «не меньше 0» у тела-свёртки ядро берёт началом и шагом, и два утверждения библиотеки переехали из сетки в доказанное — «счёт вхождений неотрицателен» (lists.flang) и «счёт по условию неотрицателен» (higher-order.flang). Осталась ВТОРАЯ половина: цель, сравнивающая не с нулём («не длиннее исходного», «равно длине»), — это уже заказ «цель, сравнивающая не с нулём», а не свёртка | — |
| равенство списков на поверхности | «обратить дважды — тождество» не ЗАПИСЫВАЕТСЯ | FLANG_TYPE и FLANG_NOT_TOTAL — заказ к языку, не к ядру |
| начальная алгебра строки | 19 функций strings.flang, из них доступна одна | FLANG_PROOF_INDUCTION_TYPE: функтор строки другой, и это названо |
ДВА ПУНКТА ЗАЧЁРКНУТЫ, И ВТОРОЙ — НЕ ЭТОЙ РАБОТОЙ. Замер снимался на дереве ветки, а в собранном дереве развёртка определений уже была (work/kernel-unfold); при сборке work/svodka2 требования теста перевёрнуты с «обязано отвергнуться» на «обязано доказаться», потому что стеречь слабость ядра и краснеть на его усилении — это сторож наоборот. Сколько утверждений библиотеки от этого переехало из сетки в доказанное, здесь НЕ пересчитано: разложение ниже снято на дереве ветки, и числа в нём читать надо как её замер.
Первый пункт был качественно иным: остальные про силу правил, а он про то, что доказательства не с чего НАЧАТЬ. Он закрыт, и польза видна не только в числе доказанного: раньше у всех пятнадцати утверждений о lists.flang причина была одна, теперь у каждого своя — 15 = 2 доказано + 8 (тело — свёртка или встроенная форма) + 4 (цель сравнивает не с нулём и не на равенство) + 1 (базе не хватает развёртки определения). Разбор стоит исполняемым замером в проверке утверждений библиотеки.
Пункт про тело-свёртку оказался ровно таким дешёвым, как здесь и было сказано: свёртка — катаморфизм ТОЙ ЖЕ начальной алгебры, из которой читается принцип индукции (раздел 3а). Закрыт он 16 августа 2026 и закрыт не катаморфизмом в общем виде, а двумя посылками — началом и шагом (раздел 3б-секстэ): для цели «не меньше 0» этого хватает, а посылок с ветвей разбор брать не приходится вовсе. Из восьми утверждений о lists.flang, у которых тело не разбор, доказано одно; остальные семь упираются уже не в свёртку, а в цель другого вида.
Поиск доказательства: недоверенный оракул над ядром
Здесь стояло, и остаётся стоять: ядро ничего не ищет (раздел 3). Поиск появился не в ядре, а РЯДОМ с ним — отдельным поиском на JavaScript, — и граница между ними жёстче, чем между тремя слоями выше: оракул предлагает кандидата, ядро проверяет его ТЕМ ЖЕ кодом, каким проверяет написанное человеком.
Корректность поиска доказывать не нужно, и это не поблажка, а всё устройство. Он вправе быть перебором, эвристикой, чем угодно; соврёт, ошибётся или пошутит — ядро отвергнет. Ровно это и делает затею безопасной: в контур доверия оракул не входит ни одной строкой, и читать его целиком, чтобы поверить ведомости, не надо.
Улика ДО: сколько корпуса ждало ненаписанной цепочки
Число снято прогоном ДО всякой правки, и оно решало, стоит ли работа своей цены. Свод корпуса: высказано 53 утверждения, 33 доказано ядром, 20 не доказано. Сегодня это неправда — корпус с тех пор вырос, — и число оставлено надгробием того дня, а не сводом на сейчас: сегодняшний свод печатает flang/scripts/proof-ledger.mjs. Из этих двадцати ненаписанной цепочки не ждёт НИ ОДНО: каждое упирается в правила ядра, а не в автора, и это проверено вопросом к свести у каждой посылки принципа:
| почему поиск не берёт | сколько |
|---|---|
начинать не с чего: у функции нет параметра с начальной алгеброй, разобранного разбор на верхнем уровне тела (тело — свёртка, если, встроенная форма) | 14 |
| цепочки нет: посылки построены, но заключение не сводится ни одним из трёх правил ядра, и закрыть их нечем | 6 |
Раскладка печатается той же командой, что и всё остальное (node flang/scripts/proof-search.mjs), а не пересказывается по памяти.
Отсюда честный вывод, который стоит сказать прямо: сегодня поиск не прибавляет к ведомости ни одного «доказано», и ведомость до этой работы и после совпадает знак в знак. Прибавит он в тот день, когда у ядра появится новое правило, — и тогда ему не понадобится, чтобы кто-то писал цепочки руками.
Второе число: ПЕРЕВЫВОД — 11 из 11
«Нашёл 0 нового» само по себе не говорит, работает ли поиск вообще, поэтому считается и обратное. У каждого доказательства индукцией, написанного в корпусе РУКОЙ, теорема снимается из исходника, и оракул ищет её заново. Их одиннадцать, и все одиннадцать он находит сам — перечисление «Светофора» (три случая, все примером), объявленные суммы, ВСТРОЕННЫЙ список и цель-равенство «копия равна оригиналу». Считает это node flang/scripts/proof-search.mjs, и находкой считается только вердикт ведомости, а не мнение оракула.
То есть работа снимает не недоказанность, а РУЧНУЮ РАБОТУ: для класса, который ядро уже умеет, цепочку писать больше не надо.
Устройство: перебор в глубину по случаям, два названных предела
Спуск идёт по СЛУЧАЯМ индукции: уровень — случай, ветка — обоснование. Отсекает не эвристика оракула, а само ядро: у вердикта есть вердикт каждого случая порознь (проверитьИндукцию, поле cases), и спуск читает его. Поэтому перебор из степенного становится линейным, а принимает по-прежнему ядро и принимает кандидата ЦЕЛИКОМ.
Обоснований в переборе два, и список закрыт: по предположению и по примеру. Недостающие два названы с доводом, чтобы список не выглядел закрытым по лени: по закону ядро принимает, но ослабляет вердикт до сетки (находка, не дающая «доказано», — не находка), а по свойству ядро сегодня отвергает всегда (сличения по вызову у него нет). Появится сличение по вызову — строка встанет в список, и находок станет больше без единой правки в ядре.
Пределов два, и оба печатаются в отказе, когда упёрлись, — тем же приёмом, каким говорит о себе ПРЕДЕЛ_РАЗВЁРТКИ:
| предел | сколько | чем держится |
|---|---|---|
ПРЕДЕЛ_ШАГОВ | 256 | сколько кандидатов оракул вправе предъявить ядру. Замер: самая дорогая находка корпуса стоит 9 кандидатов со спуском и 28 без него — это и есть цена отсечения по вердикту случая, названная числом |
ПРЕДЕЛ_ГЛУБИНЫ | 8 | докуда спуск идёт по случаям. У суммы с двадцатью вариантами перебор был бы честным и всё равно бесполезным |
Ни то, ни другое число НЕ про состоятельность: оракул ничего не доказывает, поэтому остановиться раньше — значит найти МЕНЬШЕ, а не найти ложное. «Не нашёл» и «не искал дальше» — разные ответы, и отказ их различает.
Цепочка внутри случая длиной в один шаг, и это решение: сегодня ни одно правило не читает списка известно (flang/self/proofterm.flang), значит второй шаг мог бы только ослабить вердикт. Появится правило, которое известно читает, — здесь придётся завести третий предел, и это будет решение, а не случайность.
Находка — ТЕКСТ на языке, а не дерево, и это вторая половина недоверия
Оракул мог бы отдавать ядру готовый узел AST. Он отдаёт СТРОКИ теми же восемью словами, какими доказательство пишет человек, и текст проезжает ОБЫЧНЫЙ разборщик и ОБЫЧНЫЙ слой обязательств. Там стоят проверки, которых у ядра нет: утверждение теоремы обязано совпасть с постусловием слово в слово (FLANG_PROOF_CLAIM_MISMATCH), переменная — с параметром по имени И типу (FLANG_PROOF_VAR_TYPE). Отдай оракул дерево — эти проверки остались бы у него внутри, то есть их не было бы.
Строка утверждаем при этом не печатается вовсе, а вырезается из исходника — куском той самой строки, где автор написал обеспечивает «имя» <утверждение>. Довод прямой: печати выражений на flang в дереве нет (восемь целей печатают в чужие языки), а сочинённая цель — единственная ошибка оракула, которую ядро не поймало бы: оно проверяет доказательство ОТНОСИТЕЛЬНО цели. Поэтому цель здесь не сочиняется, а переписывается, и совпадение сверяет слой обязательств.
Изъятие: ядро не верит оракулу ни на грамм
Свойство, ради которого всё это безопасно, нельзя показать на честном оракуле: зелёный цвет означал бы только, что оба согласны. Поэтому у оракула есть рубильник ЛГАТЬ, и он не для отладки. Изъятий три, и все три стоят проверками в проверке поиска доказательства:
| ложь | что подменено | кто отвергает | код |
|---|---|---|---|
| перестановкой | обоснования первого и последнего случая поменяны местами: в базе оказалось по предположению, в шаге — по примеру | ЯДРО | FLANG_PROOF_INDUCTION_STEP |
| примером | ссылка на пример, которого у функции нет | ЯДРО | FLANG_PROOF_STEP |
| целью | утверждаем не то, что обещает функция | слой обязательств, ДО ядра | FLANG_PROOF_CLAIM_MISMATCH |
Проверяется не только отказ, но и то, что доказанность не прибавилась ни на единицу: у файла бывают и другие утверждения, и считать их за находку ложного кандидата значило бы мерить не то. Мерка снимается с того же файла БЕЗ всякой теоремы, и прирост обязан быть ровно один и ровно от честной находки.
Портится при этом НАЙДЕННОЕ, а не поиск: подмени шаг раньше — и перебор просто не нашёл бы ничего, то есть изъятие показывало бы не то.
Поиск не обязателен, и это держится не обещанием
Ведомость от оракула не меняется, потому что его никто не зовёт: ни ядро, ни flang check, ни печать. Это проверяется чтением исходников — появись import … search.mjs в рабочем пути языка, проверка покраснеет и потребует объяснить, зачем поиск попал в контур. «Доказано» в flang check --proof означает «доказательство написано в файле и принято ядром», а не «инструмент сумел бы его написать».
Найденное поэтому печатается текстом, который человек впишет в файл сам (или впишет node flang/scripts/proof-search.mjs --write <файл>), а не подставляется за него молча.
Граница, названная прямо: поиск пишет на ОДНОЙ поверхности
Печать находки знает русские слова и только их. Строка утверждаем при этом берётся у автора и потому остаётся на его поверхности — то есть в английском файле нашлась бы теорема, у которой скелет русский, а утверждение английское. Разбору это не мешает (таблица ключевых слов одна на обе поверхности), читателю мешает.
Сегодня на это не попасть: у английских файлов корпуса нет ни одного утверждения, которое поиск берёт, — все они закрыты ядром без теоремы. Поэтому граница названа, но не измерена, и сказать «работает на двух поверхностях» было бы неправдой. Закрывается она тем же способом, каким слова слоя доказательства встали на две поверхности: словарём печати, а не вторым оракулом.
Чего эта работа не отменила
Прежняя запись «тактик нет и не будет» (раздел 1) остаётся верной знак в знак, и путать её с этим разделом нельзя. Тактика — метапрограмма на самом языке, ворочающая состояние цели; её не написать, потому что в flang всё либо тотально, либо объявлено обычным. Оракул — не тактика: он не часть языка, живёт вне его, а на выходе даёт не состояние цели, а ТЕКСТ доказательства, который язык разбирает обычным разборщиком. Долг поверхности от него не вырос ни на слово: ни одного нового ключевого слова не заведено.
Что дальше
- ~~Типизировать гипотезу
дано— долг выше, он первый.~~ Сделано 16 августа 2026 и слито в это дерево (веткаwork/tipizirovat-dano,5b68cbf): проходcheckTheoremsстоит последним вcheckTypesу свидетеля (flang/self/types.flang),«Проверить теоремы»— у эталона (flang/self/types.flang); гипотеза читается общим выводом типа, тип обязан бытьпризнак, отказ — тот жеFLANG_TYPE, что у постусловия и предусловия. Улика: три программы, дававшиеvalid: trueна невычислимой гипотезе, отвергнуты. Корпус не сдвинулся: гипотез-утверждений в нём 0 при 75 теоремах — дыра закрыта до того, как её начали использовать, а не после; аксиом по-прежнему ноль. - ~~Аксиомы арифметики над
неотрицательное— закрывает рекурсивный случай.~~ Сделано иначе, и это стоит сказать прямо. Рекурсивный случай закрыт без единой аксиомы: вместо того чтобы принять на веру факты порядка, ядро получило ТРИ ПРАВИЛА СВЕДЕНИЯ, каждое из которых — доказанная теорема о IEEE-754 (раздел 3в). Точный типнеотрицательноеот этого нужен не меньше, но нужен он теперь для ДРУГОГО: для утверждений вида «х минус 1 меньше х», которые на IEEE-754 ложны и правилом стать не могут никогда.
1-бис. ~~Проверять --args по объявленным типам~~ — закрыто на ветке work/entry-types-113 (checkArguments зовётся до вычисления). Это и был отвечающий, которого не хватало потолку: пока его не было, ядро читало у неотрицательное только дно. Теперь читает обе границы (раздел 3б-кватер).
- ~~Продукция в самоприменённом парсере — закрывает долг выше.~~ Сделано 15 августа 2026, и долг оплачен полностью: семь отложенных утверждений написаны у самих функций. Осталась ПОЛОВИНА того же пути — перенести к функциям теорему с индукцией и прогнать на ней побайтовую сверку; пока этого нет, пять
corpus-*.flangстоят на месте, а восьмое словообеспечиваетв этих каталогах ловит сторож в проверке ядра. Ни индукция, ни объявленный тип, ни его потолок, ни развёртка определения долга не увеличили: новых слов поверхности не заведено ни одного, и всё работает восемью уже стоящими. Развёртка не потребовала девятого слова намеренно: она не обоснование шага, а переписка нормализации, и назвать её словом значило бы дать автору распоряжаться тем, чем распоряжается правило.
2-бис. Первая половина сделана: у поля записи появился отвечающий. recordType в типизаторе на JavaScript сверял поле через годится, а не через sameType, и поле больше не ОТМЫВАЕТ значение (раздел «Узкое место»). Цена измерена на всём дереве: одна диагностика из 183 файлов, в flang/self/monoid.flang, и та по делу — «номер»: неотрицательное принимал сумму двух неотрицательное; поле объявлено число. Осталась вторая половина: завести ядру факт — объявленный тип поля наравне с типом аргумента и типом поля варианта, источником по образцу известноеПоТипу, а не новым решающим правилом. Стережёт это исполняемая улика в проверке ядра («ДЫРА ЗАКРЫТА»): заведут факт молча — она покраснеет.
- Сличение по вызову —
по свойству «…»сегодня отвергается, когда надо свестирезультатшага с результатом другой функции. Это не поиск, а чтение подстановки с узла вызова, и делается тем же приёмом. Развёртка определения (раздел 3б-бис) этого не закрыла, и путать их нельзя: развёртка читает ТЕЛО чужой функции,по свойству— её ОБЕЩАНИЕ. Там, где тело разворачивать нельзя (рекурсия по числу, чужой модуль,неотрицательноебез конструкторов), обещание остаётся единственным, на что можно опереться. С этого дня у пункта есть вторая половина выгоды:по свойствувойдёт в закрытый список обоснований поиска (раздел «Поиск доказательства»), и цепочки с ним никому не придётся писать руками. 13 сентября 2026, задача 1261 (ADR-0037 §4.1) — та же дыра со стороны СНЯТИЯ ПРЕДУСЛОВИЯ закрыта. Снятиетребуетна месте вызова читало только собственныетребуетвызывающей:«Корень» от («Удвоить» от 1)отвергалось в языке, где «удвоение неотрицательно» доказано и напечатано в ведомости, ах: неотрицательноеу параметра вызывающей не снималодовод не меньше 0. Теперь «Места функции» подают сведению те же три источника, что и доказательство постусловия: собственныетребует, объявленные типы параметров и «Постусловия вызванных» по доводу — только доказанные и только у вызванной, все места которой оплачены. Круг «снятие ↔ восьмой ход» идёт неподвижной точкой (proofterm.flang, «Снять предусловия»), без графа вызовов; обещание, написанное и не доказанное, фактом не становится. Имя изпустьна месте довода подставляется своим значением до сведения. Проверено толкованием исходников семенем: пять честных подач — код 0, две подделки —FLANG_PRECONDITION_CALL; пробы —flang/proof/examples/ precondition-discharge-reads-proved-facts.flangиforgery-…. В ведомость и в запись это не идёт: предусловия в числитель доли не входят (задача 1729). Словопо свойствув доказательстве от этого не изменилось — пункт про него остаётся. - Упрощатель — условная переписка с проверкой завершаемости самой переписки (устройство подсмотреть у Isabelle). Ею ядро будет проверять коммутацию диаграмм: доказательство равенства становится коммутирующей диаграммой, и ядро проверяет коммутацию перепиской. Нормализация из
flang/self/proof-kernel.flang— её первый и самый узкий кусок: четыре переписки, из них одна — развёртка определения под четырьмя названными ограничителями. Именно упрощатель закроет посылку с непрозрачными полями без частей (случай«Есть» содержит значение: «А»), а вместе с ней — законы монады и изоморфизма, которые сегодня стоят в ведомости сеткой 6 и 2 значений. - Решатель для линейной арифметики — обязательства для него уже лежат данными, ему останется их прочитать.
- ~~Заключение посылки для тела-свёртки~~ — СДЕЛАНО 16 августа 2026 (раздел 3б-секстэ), и сделано не так, как здесь предполагалось. Строить заключение посылки не понадобилось вовсе: у свёртки есть начало и шаг, и для цели «не меньше 0» это и есть две посылки индукции по списку. Охват по программам репозитория вырос с 17 функций до 43 — вместе со вторым правилом того же дня (результат встроенной формы), и цена каждого измерена порознь: правку снимали и смотрели, что сломается. ЧТО ОСТАЛОСЬ ОТ ПУНКТА: цель, сравнивающая НЕ С НУЛЁМ («не длиннее исходного», «равно длине»), — но это уже пункт про правила сведения, а не про свёртку: тело её ядро теперь читает.
- ~~Начальная алгебра строки~~ — есть, двумя чтениями. Разбор строки на
пусто/голова и хвост— принцип с 20 августа 2026 (c14cd749e): голова — «строка длиной 1» («Тип знака»), местоX— только хвост; довод «голова строки — тоже строка» снят тогда же, тип знака на поверхность не вышел. Свёртка по знакам строки —свёртка (разложить Т на символы) …, так в библиотеке пишут спуск по строке чаще разбора (44 свёртки по разложению вflang/stdlib, из них 19 — тело функции целиком;grepна стволе05e1c8c4d, 15 сентября 2026), — с задачи 7359 получает тот же принцип свёртки по числу витков, что у списка-параметра (правило Инд4): элемент витка объявлен знаком, пройденный кусок — списком знаков, а строка в цели заменена склейкой куска. Обратный ход — Тожд3:соединить (разложить Т на символы) по ""естьТ.strings.flangкомпилятором ветки, напечатанным в JavaScript (15 сентября 2026, над05e1c8c4d): доказано 64 → 67, сетка 55 → 53, «при условии» 1 → 0 — «Символы» (склейка разложения), «Последний символ» (длина не больше знака, индукцией по свёртке) и «Палиндром» (его теорема стояла на недоказанном свойстве «Символы»). - Обоснования поиска:
по свойствуи цепочка длиннее одного шага. Оба ждут не поиска, а ядра: первое — сличения по вызову (пункт 3), второе — правила, которое читает списокизвестно. Пункт стоит здесь, чтобы усиление ядра сразу считалось и на этой стороне: сегодня поиск не находит по программам репозитория ни одного нового доказательства (раздел «Поиск доказательства»), и ноль этот стоит исполняемой проверкой, которая покраснеет в тот же день.
Чем эти пункты отличаются от закрытого первого. Все они про силу правил; первый был про то, что доказательства не с чего НАЧАТЬ. Разница видна числом: он один сдвинул «доказано ядром» с 6 на 9 и «доступно индукции» с 28 функций библиотеки на 50, не прибавив к ядру ни одной аксиомы и ни одного нового правила сведения — только чтение объявления, которое у языка уже было.
- Шире прочитать спуск по числу. Принцип по отрезку (раздел 3г) читает ровно одну форму спуска —
н минус 1— и пять форм условия дна. Список закрыт не по лени: каждая новая форма обязана приехать с рассуждением о IEEE-754 и о том, что цепочка спуска дна не проскакивает. Ближайшие честные кандидаты видны на программах репозитория:н минус 2при днен не больше 1(«Через два»изdocs/examples/measure/natural.flang) законен, но требует посылки «шаг вниз не больше, чем ширина дна плюс единица», и написать её надо доказательством, а не удобством. То же про меру, отличную от самой переменной: сверять её с аргументом вызова ядру сегодня нечем, и сделать вид, что сверило, — хуже, чем отказать. - Условная форма утверждения. Ровно на неё упирается строка заказа «Фибоначчи», и упирается она НЕ в индукцию: у самой
«Фибоначчи»тело — вызов, а неесли, а лемма о«Фибоначчи шагом»была НЕПРЕДСТАВИМА, потому что «результат не меньше 0» о ней ложь (накопитель объявленчисло). Верна там условная форма — «если накопители неотрицательны», — и записать её стало чем: словотребуетприехало 16 августа 2026, лемма записана двумя предусловиями, и невысказываемой она быть перестала. Недоказанной осталась: теоремы при ней не написано, и отчёт о доказательствах честно говорит «сетка». Разницу между «недоказуемо» и «невысказываемо» путать по-прежнему нельзя — просто теперь это про другое.
Мера списка: правило 4 читает длина как то, что построило тело
Что было измерено до работы. На 114 утверждениях flang/stdlib ядро закрывало 23 и отвергало 91. Из отвергнутых 27 имели вид (длина результат) не больше (длина <аргумента>), и правило 4 их ДОСТАВАЛО — граница у них терм, а не литерал, — но не закрывало ни одного. Правый край сравнения был не при чём: подставив тело на место результат, слева получаем не терм с границами, а ФОРМУ ТЕЛА (разбор — 8, свёртка — 6, отбор — 3, пусть — 3, вызов и выбор — по одному).
Что прибавилось. Пять случаев, и каждый — теорема о вычислителе, читаемая у flang/self/interpret.flang и flang/self/builtins.flang, а не соглашение:
- Переписка
длина(…)равным выражением (мераПостроения). Список форм ЗАКРЫТ и в нём только те, у которых длина итога определяется точно: выписанный целиком список (startSeqпоitems), строка-литерал (длинарежет по кодовым точкам, и здесь считается тем жеArray.from), выбор и разбор (значение есть значение одной ветви), отображение (stepLoopприmode === "map"кладёт ровно одно значение на виток),добавитьиприписать(продлить/приписатьдописывают ровно одно). Формы, где длина зависит от данных —отфильтроватьи свёртка, — сюда не входят ни строкой. - Разбор под границей (
поРазбору). Доказать «тело не больше Г» в каждой ветви и есть доказать цель. Внутри ветви разбираемое ИМЯ заменяется конструктором: это уравнение написано автором словомслучай, и та же подстановка строит его для принципа индукции (flang/self/proof-initial.flang). Подстановка делается ТОЛЬКО для встроенного списка, у которого части названы целиком и всегда; увариант «C» с полем как имяавтор вправе назвать не все поля, и собранная из названного конструкция была бы не тем значением. Захват имени отвергается, а не обходится. - Отбор не удлиняет:
длина(отфильтровать L где p) ≤ Г, еслидлина(L) ≤ Г. - Свёртка, растущая не быстрее списка. Индукция по списку тремя посылками: мера начала есть НОЛЬ; шаг прибавляет не больше единицы; шаг НЕ ПОРТИТ ЗНАКА. Третья несёт вес:
М(акк) ≤ М(акк) + 1на не-числе ложно, и допущение «М(акк) не меньше 0» — то самое доказательство того, что не NaN, что у правила 1, но даром его взять нельзя: оно само доказывается той же индукцией. Мер ровно две — само значение (накопитель число) и его длина (накопитель список). Граница обязана быть длиной ТОГО ЖЕ списка, и сверяется это знак в знак. - Неотрицательный конечный литерал в границе: из
Е ≤ ГследуетЕ ≤ К + Гпри конечномК ≥ 0. Посылку «Г не есть NaN» несёт сам доказанный порядок: еслиЕ ≤ Гистинно, ни одна сторона не NaN.
Плюс у правила 1 появился случай разбор (значение есть значение одной ветви, известное о пересвязанных именах вычёркивается), а у правила тождества — та же переписка длины на обеих сторонах. Посылка «не NaN» тождеству не нужна, и это проверено вычислением, а не памятью: равен языка есть Object.is (flang/self/builtins.flang), на нём (0 делить на 0) равен (0 делить на 0) даёт да, а 0 равен (0 умножить на −1) даёт нет. Порядок ведёт себя иначе: (0 делить на 0) не больше (0 делить на 0) даёт нет.
Что закрылось — числом и именами. По flang/stdlib отвергнутых 91 → 77, закрытых 23 → 37; обратно не раскрылось ни одного. Четырнадцать закрытых: dictionary «Размер», «Убрать»; hashmap «Путь хеша»; higher-order «Отобразить», «Отобразить составом», «Отфильтровать», «Считать где»; lists «Без значения», «Считать вхождения», «Уникальные»; sets «Размер множества», «Убрать из множества»; strlists «Непустые», «Хвост строк». По всем программам репозитория отчёт о доказательствах: доказано ядром 170 → 187, сетка 124 → 107, и все семнадцать — БЕЗ ЕДИНОЙ ТЕОРЕМЫ (82 → 99). ПЕРЕМЕРЕНО НА СТВОЛЕ ПРИ СЛИЯНИИ (19 августа 2026): точка отсчёта у ствола другая, и та же работа даёт доказано ядром 185 → 203, сетку 124 → 107, без теоремы 96 → 114.
Чего правило не берёт, и это названо, а не умолчано. Из оставшихся 77: 26 — цели «равно», где тождество сличает стороны синтаксически (нужно симметричное правило роста: «растёт РОВНО на один» — НАПИСАНО, см. У3 и улику «Копия свёрткой»; заказ закрыт, число 26 относится ко дню замера); 12 — «не меньше 0» у рекурсивного тела, где шагу нужно допущение о рекурсивном вызове, то есть ИНДУКЦИЯ, а на пути объявления её не зовёт никто; 18 — порядок, где границы не выведены; 12 — вид цели вне всех правил; 9 — ограниченность потолком.
Подделки. Пятнадцать целей сверены у обеих реализаций ЦЕЛИКОМ, включая текст отказа (сверка ядра с двойником), и семь из них — подделки: отбор из чужого списка, перевёрнутое сравнение отбора, рост свёртки вдвое, свёртка по чужому списку, начало свёртки не ноль, накопитель, становящийся не-числом, добавление, будто бы не удлиняющее. Плюс главная: (0 делить на 0) не больше (0 делить на 0) остаётся отвергнутой. Снятие каждого случая роняет свой тест — проверено пятью прогонами, в каждом из которых одна правка снята.
Три факта о длине строки, и узким местом был не факт, а место чтения
Что было измерено до работы. Принцип индукции по строке закрыл ОДНО утверждение на всю библиотеку и все примеры. Отказ не исчез, а сместился: было «у этого типа принципа индукции нет», стало «ничего не знаю о длине». Заказ назвал три недостающих факта — длина головы, уравнение звена, длина склейки.
Заведены все три, и вот они.
длина головаесть 1 (длиныПоТипу). Голова у образцаголова и хвост— ровно один знак: случайголова и хвосттем и отличается отпусто, что строка непуста, и тем же одним знаком связывает её типизатор («Связать части строки»). Типстрока длиной 1дописывает ровно одно место —объявлениеСтроки, — а разборщик такого узла не строит вовсе: в исходнике тип строки пишется одним словом. Читается ТОЛЬКО единица. У типизатора то же слово значит НИЖНЮЮ границу («длина не меньше N»), и принять её за точную длину было бы ложью на первой же склейке. Факт отдаётся РАВЕНСТВОМ: равенство уже читают все правила — им переписываются обе стороны цели, им закрывается цель-допущение, из него жеизвестноеСверхуберёт потолок.- Уравнение звена —
длина(звено{г, х})есть1 плюс длина(х). Оно у ядра БЫЛО, и на строке работало и до этой работы: конструкторы у строки и у списка читаются одной функцией, независимо от типа разбираемого. Не хватало не уравнения, а его ОХВАТА: переписка стояла ВНУТРИ двух правил из шести — у порядка и у тождества. Цельдлина "" не больше 1— это правило ограниченности, и оно о мере длины не знало ничего. Теперь переписка стоит ОДИН раз, до разбора цели по видам, и одной и той же функцией переписываются цель и допущения: перепиши одну сторону — и допущение перестало бы сходиться с целью знак в знак. длина (соединить А с Б)естьдлина А плюс длина Б(мераПостроения). Держится это на том, что у строки flang мера ОДНА — знаки (docs/flang/SPEC.md, «У строки одна мера — знаки»). Склейка, которая слила бы высокую и низкую половины суррогатной пары в один знак, ОТКАЗЫВАЕТ у трёх целей печати с UTF-16, а у остальных пяти такого стыка не бывает по построению. Значения, о длине которого шла бы речь, не появляется — поэтому равенство верно всюду, где склейка вообще даёт значение. Список закрыт ДВОИЧНОЙ формой:соединить L по Р— другой узел, и длина его зависит от числа элементов, которого ядро не знает.
Заодно: равенство читается наравне с границей. известноеСверху брал только допущения Т не больше Л. Т равно Л говорит о Т больше, а не меньше, и теперь читается тем же отбором и с той же сверкой границы: без сверки правило приняло бы Т равно 11 за доказательство «Т не больше 10».
Что закрылось — числом. Отчёт о доказательствах по всем программам репозитория (proof-ledger.mjs, оба двоичных напечатаны из своих же исходников): доказано ядром 364 → 367, индукцией 52 → 54, сетка 584 → 581; высказано 961 и там и там, отвергнутых 0, нарушенных 0, «объявлено, не доказано» 13 и там и там. Прирост поимённый, по пофайловому отчёту (--files, 466 строк, сверены целиком): stdlib/higher-order «точка удлиняет строку ровно на один знак» (склейкой) и «начало не длиннее целого» в ДВУХ файлах docs/examples/wal (индукцией по строке плюс ограниченность). Изменились ровно эти три файла, и все три ВВЕРХ — ни одна программа репозитория не потеряла ни одного доказательства. Три факта дали три утверждения, и это меньше, чем ожидалось.
Почему так мало — причина названа, а не угадана. У strings.flang «Первый символ» цель (длина результат) не больше 1 теперь сводится, но само постусловие — КОНЪЮНКЦИЯ из трёх частей, и две другие («текст начинается с результат» и «пусто ровно у пустой») правилам ядра по-прежнему не по зубам. Доказывается постусловие целиком или никак, поэтому закрытая треть не считается ни за что. То же у http, json и postgres: там строка не разбирается ни в одном теле, и фактам о частях строки не к чему прицепиться.
Подделки — по паре на факт (flang/test/fixtures/poddelka-dlina.flang, проверял прогон подделок на JavaScript). Честная половина обязана быть доказана обо ВСЕХ входах, поддельная — остаться недоказанной; правило, отказывающее всему подряд, отвергает и ложь, и правду, и подделка о таком правиле не меряет ничего. Ложность каждой подделки проверена прогоном, а не рассуждением: flang run … --args отвечает FLANG_PROPERTY на названном входе.
Снятие правки — что перестаёт доказываться (на реализации ядра для JavaScript, где снять факт стоит секунды):
| снято | «Первый знак» шаг | «Первый знак» база | «Снова целое» шаг | «Склеить» |
|---|---|---|---|---|
| ничего | ✓ | ✓ | ✓ | ✓ |
| факт 1 | — | ✓ | — | ✓ |
| факт 2 (место чтения) | ✓ | — | — | ✓ |
| факт 3 | ✓ | ✓ | — | — |
И главная проверка — не «изменился ли вердикт», а «стал ли он НЕВЕРНЫМ»: прочитай факт 1 как «длина головы = 0», и ядро ДОКАЗЫВАЕТ подделку «в первом знаке знаков ноль» — про программу, которая на входе "абв" возвращает "а".
Доказательство как предмет: запись и её сверка
Чего не было
Ядро говорило «доказано» и на этом кончалось. Терм доказательства оно строит внутри себя, но наружу отдавало одно слово. Взять доказательство, положить в файл, отдать другому и перепроверить чужими руками было нельзя — а «доказуемый» значит именно это: не «мне сказали», а «вот доказательство, проверь сам».
Цена называлась числом: чтобы верить слову «доказано», приходилось верить 3336 строкам решающего кода трёх файлов ядра — их считает bootstrap/flang io flang/scripts/kernel-lines-to-trust.flang --plan 'Потолок не пробит'.
Что теперь есть
flang check <файл> --proof --записать <куда>
кладёт доказательство рядом с программой — строками, которые читает человек:
запись доказательства 1
исходник flang/proof/examples/body-forms.flang
строк 174
знаков 7280
отпечаток 123456789 987654321
ядро 2
утверждений 4
утверждение «высота неотрицательна» функции «Высота» строка 152
вид postcondition
вердикт доказано
теорема «высота неотрицательна» строка 166
дано «стопка» строка 167
цель строка 168
индукция по «стопка» строка 169
случай строка 170 шагов 1
шаг 1 строка 171 закрывающий по предположению
следовательно доказано да
принцип тип «Стопка» по «стопка» носитель algebra база 1 шаг 1
сведение «неотрицательность по построению»
посылка «Дно» вид base вариант «Дно» вердикт доказано закрыта reduction шагов 0 правило «…»
посылка «Слой» вид step вариант «Слой» вердикт доказано закрыта term шагов 1 правило «…»
конец утверждения
Термов целей и допущений в записи НЕТ ни в каком виде, и это решение, а не пропуск: записан НОМЕР СТРОКИ, по которому цель читается в исходнике. Пересказ цели был бы вторым источником одного и того же и разошёлся бы молча; номер строки не расходится, потому что читается там же, где написан.
Сверщик
flang/proof/checker.flang — отдельная программа на flang, не ввозящая ни одного модуля (это условие: её копируют одним файлом). Берёт исходник и запись, отвечает кодом 0 или 1, сама ничего не доказывает.
bootstrap/flang io flang/proof/check.fscript --timeout 7200000 -- <исходник.flang> <запись> [<сверщик.flang>]
План check.fscript копирует сверщика во временный каталог, кладёт рядом файл «наряд» с двумя путями и исполняет копию. Код 0 — сошлось, 1 — не сошлось и беда названа, 3 — сверка не дошла до конца (нет файла, кончились шаги).
Что она проверяет САМА, не спрашивая ядра ни о чём:
- привязку к исходнику — число строк, число знаков и два многочленных отпечатка. Запись, снятая с другой программы, отваливается здесь;
- что запись не выдумана — утверждение обязано стоять на названной строке, теорема на названной, и каждый шаг обязан быть НАПИСАН в исходнике теми же словами;
- что шаги сошлись целиком — сверщик сам выписывает из исходника все значащие строки теоремы («дано», «утверждаем», «индукция по», «случай», шаги, «следовательно доказано») и сличает список с записью;
- что цель закрыта — в каждом случае обязан быть шаг без выписанного утверждения, и обязано стоять «следовательно доказано»;
- что ни одна теорема исходника не замолчана записью;
- что круга нет — ни прямого («А» через «А»), ни через других («А» через «Б», «Б» через «А»); считается замыкание по достижимости;
- что случаи покрывают тип — у индукции по объявленной сумме посылок ровно столько, сколько вариантов, и варианты те же;
- что правило сведения названо из закрытого списка — правило, которого в списке нет, отвергается. Имён в списке тринадцать плюс пустое имя (посылка, закрытая без правила), и это ровно те имена, которые ядро способно поставить в поле правила.
Список правил сверяется с ядром прогоном, а не обещанием
bootstrap/flang io flang/proof/rules-match.fscript bootstrap/flang io flang/proof/rules-match.fscript -- --list-only код 0 — сошлось; 1 — разошлось, и расхождение названо поимённо; 3 — не проверено (нет двоичного, файла или ключ не тот)
Ключ --list-only спрашивает одну первую половину. Пути берутся из переменных RULES_KERNEL, RULES_TERM, RULES_CHECKER, RULES_SOURCE, RULES_SECOND_SOURCE, RULES_RULE, двоичный — из FLANG_BIN.
Первая половина берёт имена из исходника ядра — доводы «Принято правилом», ровно одного места, где это поле заполняется, — берёт список из исходника сверщика и сличает их знак в знак. Вторая половина подставляет каждое названное имя в настоящую запись доказательства: сверщик обязан её принять, а запись с именем вне набора — отвергнуть и назвать имя. Второго списка имён нигде нет: оболочка берёт их у первой половины и гоняет.
Проверка заведена после разбора 23 августа. Список писался на глаз и разошёлся с ядром В ОБЕ СТОРОНЫ. Семь имён ядро ставило, а в списке их не было, — и честная запись честной программы (flang/test/fixtures/ohrana-daet-fakt.flang, посылка шага закрыта правилом «разбор цели по условию») отвергалась как подделка. Ещё два имени в списке числились и не встречались никогда: обеими развёртками ядро помечает ДРУГОЕ поле, «развёрнуто», а оно в запись не едет вовсе. Значит правил не восемь и не пятнадцать, а тринадцать: 6 из прежних восьми + 7 недостающих.
Тем же прогоном нашлась третья беда: список спрашивался ТОЛЬКО у утверждения, при котором в исходнике написана теорема. Посылки с названным правилом ядро ставит и там, где теоремы нет ни строки (flang/proof/map/substantive.flang, «С5 сохранение: длина выросла на один»), и такая запись проходила мимо закрытого списка целиком — в ней можно было назвать любое правило. Теперь список спрашивается на обеих дорогах, и обе прогоняются.
Пошаговое сведение: язык ходов
Сведение, записанное ОДНИМ ИМЕНЕМ ПРАВИЛА, сверщик пересчитать не может — за именем стоят три тысячи строк поиска в proof-kernel.flang. Записанное ПО ШАГАМ — может, и это не то же самое усилие: искать дорого, проверять дёшево. Поиск перебирает случаи, откатывается и тянет пределы; проверка берёт названный ход, применяет его к цели и смотрит, сошлось ли. Ни перебора, ни отката, ни состояния.
Сверщик держит СПИСОК НЕЗАКРЫТЫХ ЦЕЛЕЙ. Ход применяется к первой из них и либо переписывает её, либо делит, либо закрывает. Сведение состоялось, когда целей не осталось.
Первичных ходов семь, и список закрыт:
| ход | что делает | чем проверяется |
|---|---|---|
развернуть ⟨S⟩ строка L | «Ф» от (вариант «В» с полями) меняется на тело того случая с подстановкой доводов | тело берётся ИЗ ИСХОДНИКА по номеру строки; проверяется, что строка стоит внутри функции «Ф», что над нею образец варианта «В» и что она начинается словом то |
выбор ⟨S⟩ | если да то А иначе Б — это А | условие обязано быть литералом да или нет |
замена по дано N | переписка цели равенством-допущением, во всех местах сразу | допущение N обязано быть равенством; левая сторона обязана в цели встретиться |
соседи ⟨S⟩ | перестановка двух операндов ОДНОГО узла плюс/умножить на | операндов ровно два: через скобки не переставляется, ассоциативность в IEEE-754 ложна |
деление ⟨У⟩ | цель делится надвое: У := да и У := нет | У обязано стоять в цели УСЛОВИЕМ (если У то), то есть быть признаком |
закон «Имя» | цель заменяется посылками именованной теоремы | список законов закрыт; чужого сверщик не принимает |
закрыть дано N / закрыть тождеством / закрыть истиной | цель снимается | совпадение знак в знак |
Плюс три служебные строки: ход цель (начать), ход дано N предположение по «имя» (объявить допущение индукции) и ход конец (целей не осталось).
Ни начальная цель, ни допущения в записи не написаны вовсе. Сверщик считает их сам, из исходника: цель посылки есть утверждение теоремы, в котором разбираемое имя заменено конструктором случая, а результат — вызовом доказываемой функции на нём; допущение индукции — то же утверждение на рекурсивной части, и РЕКУРСИВНОСТЬ ПОЛЯ проверяется по объявлению типа. Писать их в записи значило бы дать подделывателю место, куда вписать что угодно.
Терм живёт в записи СТРОКОЙ и одет в угловые скобки ⟨⟩ (ёлочки заняты именами). Дерева нет нарочно: дерево потребовало бы разборщика языка внутри сверщика, то есть второго компилятора. Скобки в flang стоят вокруг каждой двуместной формы, поэтому подтерм — это подстрока со сбалансированными скобками, а «заменить подтерм» — замена подстроки по пробельным краям.
Чего сверщик не делает, и это сказано прямо
СВЕДЕНИЕ, ЗАПИСАННОЕ ОДНИМ ИМЕНЕМ ПРАВИЛА, ОН НЕ ПЕРЕСЧИТЫВАЕТ. Решение «выводится ли эта цель из этих допущений» принимает proof-kernel.flang, и если в записи стоит только ИМЯ сработавшего правила, проверять нечего.
Поэтому в ответе сверщика стоят ДВА числа: сколько сведений проиграно заново по шагам и сколько правил принято на слово. Считаются при этом ВСЕ посылки с названным правилом, а не только «закрытые сведением»: посылка, закрытая шагом автора, тоже несёт правило — ядро проверило им, что заключение выводится из поданного шагом допущения.
Долги, названные прямо
- Ходы пишутся, но у восьми записей из сорока трёх. Долг стоял с 22 августа в виде «ходы никто не пишет» и был НЕВЕРЕН: мерили строку
след ядра, а строка хода зовётсяход— это две разные строки записи (разбор — задача 1303, прибор той ветки в ствол не влит). Числа ствола, снятые одной линейкой 1 сентября 2026: записей с ходами 8 из 43, утверждений 8 из 129, посылок 13 из 74, строк хода 58, строкслед ядра0. Сверщик переигрывает 13 сведений и оставляет на слово ядра 61 посылку и 93 шага. Работа остаётся — довести печать ходов до всех записей, — но она про ядро (proof-kernel.flang), а не про сверщика, и это не работа с нуля.
1а. Законов два. «Сумма неотрицательных» и «литерал неотрицателен». Правила 1, 3, 4 и 6 ядра — это перечни случаев (у одного правила 1 их одиннадцать), и каждый случай станет отдельным названным законом строк на пять. Пока их нет, пошагово записать можно только то сведение, которое этими двумя законами и семью ходами выражается.
- Ключ знает только двоичный.
--записатьразбираетflang/src/emit/c/flang_repl.c; у второй командной строки, написанной на самом языке (flang/self/cli.flang), этого ключа нет. Она в сборку компилятора не входит, поэтому расхождение сегодня ничего не ломает, но оно есть, и названо здесь, а не оставлено на память. - Один шаг на строку. Сверщик читает исходник построчно:
случайи обоснование, написанные В ОДНОЙ строке, он разложит иначе, чем разбор, и ОТКАЖЕТ. Отказ — безопасная сторона ошибки: принять он в этом случае не может, только отвергнуть честное. Вflang/proof/examplesтаких строк нет ни одной.