Numeric comparisons in conditions, and two loop defects they uncovered - #12
Merged
Conversation
Цикл по JSON-массиву объектов отдавал элемент через fmt.Sprint, то есть
Go-форматированием карты (`map[name:api restartCount:12]`). Внутри тела
разваливалось всё, что обращается к полю: `{{pod.name}}` и условие на
`pod.restartCount` находили пустоту, а модели показывался синтаксис языка,
на котором случайно написан движок.
Строка остаётся строкой, всё остальное сериализуется обратно в JSON.
Вылезло на числовых условиях: живой случай из задачи — порог внутри
for_each по подам — без этого не работает вовсе.
`var > 5`, `>=`, `<`, `<=` рядом с `==`, `is [not] empty` и `contains`.
Справа либо число, либо ИМЯ переменной: порог почти никогда не литерал,
он приезжает из шага, разобравшего запрос.
Живой отказ, ради которого это сделано: в первый же день, когда скиллы
стал писать не человек, а модель, она написала `{{pod.restartCount}} > 5`,
получила отказ разбора и написала ту же форму ещё раз другим шагом. Оба
раза — условие в теле for_each: «оставить те, что выше порога» пишут
циклом с ветвлением внутри, поэтому отдельной фильтрации коллекций не
добавлено.
Два правила против невидимо неверной ветки:
- не-число РОНЯЕТ ход, а не сваливается тихо в false. Условие —
единственное место, где неверный ответ не оставляет следа;
- пустая (или отказавшая) переменная — НЕ ноль. Прочитать её как 0 значит
сделать «шаг ничего не вернул» неотличимым от «число маленькое»; кому
пустота законна, пишет `var is not empty` рядом.
Два целых сравниваются как целые: девятнадцатизначный идентификатор не
теряет последние цифры в float64. `==` остаётся ТЕКСТОВЫМ — `"5" == "5.0"`
по-прежнему ложно, иначе поменялся бы смысл уже написанных равенств по
идентификаторам и часовым. Арифметики нет: выражения — дверь к скиллам,
которые нельзя прочитать сверху вниз.
Скобки в условии по-прежнему отвергаются (поле держит ИМЯ, одно написание),
но текст отказа теперь называет именно их и печатает условие без них.
CondVar → CondVars: сравнение называет переменную с обеих сторон, и
читатель, видевший только левую, пропускал опечатку в пороге — ровно то,
что такой читатель и ищет. Линтер W14 теперь её видит.
Переменная читалась после каждой итерации и никогда не очищалась, поэтому итерация, ничего не записавшая (ветка внутри не сработала), вносила оставленное предыдущей. Цикл, выбирающий два элемента из трёх, возвращал три, и лишний неотличим от честного результата. Теперь она опустошается перед телом. Тем же движением чинится протухшее чтение в другую сторону: шаг ниже по телу видел значение ПРЕДЫДУЩЕЙ итерации там, где эта ничего не записала. Соседняя конструкция вела себя правильно всё это время: `parallel` собирает `produced()` — только то, что ветка действительно записала. `for_each` был единственным местом с такой семантикой. Дедупликации не появилось: две итерации, честно давшие одинаковый ответ, дают два результата. Обход через пустой `else` больше не нужен (и убран из примера в README).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the embedder's task on numeric conditions:
>,>=,<,<=where==andcontainsalready live. Format 2.3.0.Why
Numbers were in the flow all along — counters, ids, thresholds parsed out of a
request — and the only thing expressible about one was equality with a literal
(
ci.id == 0as a sentinel). Everything else went into the TEXT of a step,where a deterministic rule ends up applied by a model, or into an asset: a
network call to compare two numbers.
The refusal was not hypothetical. On the first day skills were being written by
a model rather than by hand, it wrote
{{pod.restartCount}} > 5, got a parseerror, and wrote the same form again in another step of the same session. Both
attempts were a condition in the body of a
for_each.What is in
var > 5,var >= req.limit,var < 0.5,var <= days. The right side maybe a number or the NAME of a variable holding one — a threshold is rarely a
constant.
empty or failed variable is NOT zero and stops it too, with the error naming
var is not emptyas the way to allow emptiness. A condition is the one placewhere a wrong answer leaves no trace:
restarts > 5looks right whatever itreturns.
last digits to float64.
NaN,Infand hex floats are not numbers here.==is unchanged and still textual:"5" == "5.0"stays false, andcoercion would quietly change equalities that compare ids and sentinels.
{{x}} > 5), but the error now names the braces andprints the condition without them, instead of listing the allowed shapes.
for_eachplus a condition in its bodyis the shape the case actually arrives in.
Two defects the live shape uncovered
Both were blocking the very example the task is written around, and both are
separate commits.
(
map[name:api restartCount:12]). Every field lookup inside the loop —{{pod.name}}, a condition onpod.restartCount— resolved to emptiness, andthe model was shown a syntax belonging to the language the engine happens to
be written in.
collectgathered a duplicate wherever an iteration produced nothing. Thevariable was read after every iteration and never cleared, so a loop picking
two items out of three returned three, and the extra one looks exactly like an
honest result.
parallelhad it right all along (it collectsproduced());for_eachwas the odd one out.Go API
CondVar→CondVars([]string). A comparison names a variable on bothsides, and a reader that saw only the left would leave a typo in a threshold
unchecked — which is what such a reader is usually looking for. The linter's W14
now catches it.