Автор выдвигает несколько метафизические требования к минимализму числа выполненных действий, пытаясь ввести новые формы структурных операторов. Забывая, что внутри структурных операторов тоже ведь спрятаны определенные действия (передача управления, установка скрытых флагов).
Даже "лишняя" проверка булевой переменной для него уже неприемлема, хотя это всего лишь вспомогательный флаг для структуризации алгоритма. Надуманного повода со сложностью извлечения объектов будет недостаточно для такого сильного требования.
Если он хочет совсем без лишних проверок и действий, то ему, в общем случае, нужны неструктурные блок-схемы. По крайней мере, ему нужен новый структурный оператор без вложенных субоператоров (операторных параметров, которые и дают лишние проверки и действия). То есть, если поместить все элементы нужной блок-схемы на один структурный уровень - вся "спагетти" переходов между этими элементами блок-схемы перейдет в один структурный оператор со сложной передачей управления внутри себя.
Также, не следует ли объединить извлечение из вещи с проверкой ее внутренности на пустоту?
Это будет такая вот функция извлечения с побочным эффектом:
ИзвлечениеВещь (IN вещь; OUT субвещь) непустой: BOOLEAN
(*post: "субвещь" - неопределена, если "вещь" определена, но пуста*)
а также функции перехода к первому и следующему сундуку:
Первый (OUT сундук) есть: BOOLEAN
(*post: "сундук" - неопределен, если нет ни одного сундука*)
Следующий (VAR сундук) есть: BOOLEAN
(*post: "сундук" - неопределен, если исходное значение "сундук" определено, но следующего за ним нет*)
Алгоритм воплощается в виде такого структурного оператора почти с чисто логическими параметрами-выражениями, то есть почти без операторных параметров.
existDeath := FALSE; (* смерть Кощея пока не найдена *)
BEGINIF (* переход к WHILEIF, если условие истинно (первый сундук есть), и выход из оператора, если ложно *)
First(сhest) (* проверка, что первый сундук есть, и переход к нему *)
WHILEIF (* переход к следующему UNTIL, если условие истинно (сундук пустой), и к следующему ELSIF, если ложно *)
~UnpackChest(сhest, hare) (* проверка, что в сундуке есть заяц и его извлечение *)
UNTIL (* выход из оператора, если условие истинно (следующего сундука нет), и возврат к WHILEIF, если ложно *)
~Next(сhest) (* проверка, что следующий сундук есть, и переход к нему *)
ELSIF (* переход к следующему UNTIL, если условие истинно (заяц пустой), и к следующему ELSIF, если ложно *)
~UnpackHare(hare, duck) (* проверка, что в зайце есть утка, и ее извлечение *)
UNTIL
~Next(сhest)
ELSIF (* переход к следующему UNTIL, если условие истинно (утка пустая), и к следующему ELSIF, если ложно *)
~UnpackDuck(duck, egg) (* проверка, что в утке есть яйцо, и его извлечение *)
UNTIL
~Next(сhest)
ELSIF (* переход к следующему UNTIL, если условие истинно (яйцо пустое), и к следующему ELSIF, если ложно *)
~UnpackEgg(egg, needle) (* проверка, что в яйце есть игла, и ее извлечение *)
UNTIL
~Next(сhest)
ELSIF (* переход к следующему UNTIL, если условие истинно (игла пустая), и к ELSE, если ложно *)
~UnpackNeedle(needle, death) (* проверка, в игле есть смерть Кощея, и ее извлечение *)
UNTIL (* выход из оператора, если условие истинно (следующего сундука нет), и возврат к WHILEIF, если ложно *)
~Next(сhest)
ELSE (* выполнение субоператоров и выход из оператора *)
existDeath := TRUE (* смерть Кощея найдена *)
END;
Очевидно, что минимальное количество действий и проверок зависит от того, как нам доступны данные: с помощью каких структур (со ссылками, без ссылок), процедур и функций. Автор не конкретизировал используемый инструментарий.
Сегмент
BEGINIF (* переход к WHILEIF, если условие истинно (первый сундук есть), и выход из оператора, если ложно *)
First(сhest) (* проверка, что первый сундук есть, и переход к нему *)
здесь можно заменить на сегменты внешнего IF-оператора
IF (* переход к THEN, если условие истинно (первый сундук есть), и выход из оператора, если ложно *)
First(сhest) (* проверка, что первый сундук есть, и переход к нему *)
THEN (* выполнение субоператоров и выход из оператора *)
а после вложенного в него оператора цикла добавить закрывающее END для IF-оператора.
Тогда оператор цикла немного упростится.
Можно после каждого UNTIL-сегмента этого оператора цикла добавить THEN-сегмент
THEN (* выполнение субоператоров и выход из оператора *)
UnassignHare(hare); (* установка неопределенного значения переменной hare *)
UnassignDuck(duck); (* установка неопределенного значения переменной duck *)
UnassignEgg(egg); (* установка неопределенного значения переменной egg *)
UnassignNeedle(needle) (* установка неопределенного значения переменной needle *)
к которому идет переход от этого UNTIL-сегмента, если его условие истинно.
Тогда сделаются неопределенными значения переменных hare, duck, egg и needle, если смерть Кощея не была найдена. Но тогда же будут лишние затирания значений переменных "Вещь", если вещи какого-либо типа из перечисленных ни разу не находились.
Можно перед каждым ELSIF-сегментом этого оператора цикла добавить ";"-сегмент
; (* выполнение субоператоров и переход к следующему ELSIF *)
existВещь := TRUE (* вещь данного типа была обнаружена *)
к которому идет переход от предыдущего WHILEIF- или ELSIF-сегмента, если его условие ложно.
И перед оператором цикла дописать
existHare := FALSE; (* заяц пока не был обнаружен *)
existDuck := FALSE; (* утка пока не была обнаружена *)
existEgg := FALSE; (* яйцо пока не было обнаружено *)
existNeedle := FALSE; (* игла пока не была обнаружена *)
а THEN-сегменты привести к виду
THEN
IF existHare THEN UnassignHare(hare) END;
IF existDuck THEN UnassignDuck(duck) END;
IF existEgg THEN UnassignEgg(egg) END;
IF existNeedle THEN UnassignNeedle(needle) END
Тогда лишних затираний значений вызовами процедур UnassignВещь не будет, но появятся дополнительные переменные existВещь. И будут повторные (лишние) присваивания переменным existВещь в ";"-сегментах, если вещи какого-либо типа из перечисленных находились несколько раз.
Наконец, можно ";"-сегменты заменить такими же UNE-сегментами, но выполняющими свое тело не более одного раза за каждый вызов цикла, а в повторные разы сразу передающими управление следующему сегменту, как будто UNE-сегмент удален.
UNE
existВещь := TRUE
Тогда повторных присваиваний также не будет, но будут лишние передачи управления либо скрытая динамическая кодогенерация.
К сожалению, без использования динамической кодогенерации либо лишних передач управления, либо лишних проверок, присвоений или вызовов процедур невозможно сделать неопределенными значения переменных hare, duck, egg, needle в случае если смерть Кощея не была найдена.
Общая структура такого оператора цикла:
BEGINIF (* expressBoolean истинно: переход к переход к WHILEIF,
expressBoolean ложно: выход из оператора *)
expressBoolean
WHILEIF (* expressBoolean истинно: переход к первому следующему DO | THEN | UNTIL,
expressBoolean ложно: переход к первому следующему ";" | UNE | ELSIF | ELSE | выход из оператора *)
expressBoolean
( DO (* выполнение statementExpress и возврат к WHILEIF *)
statementExpress
| THEN (* выполнение statementExpress и выход из оператора *)
statementExpress
| UNTIL (* expressBoolean истинно: переход к первому следующему THEN | выход из оператора,
expressBoolean ложно: возврат к WHILEIF *)
expressBoolean
[THEN (* выполнение statementExpress и выход из оператора *)
statementExpress])
{{";" (* выполнение statement и переход к первому следующему ";" | UNE | ELSIF *)
statement
| UNE (* выполнение (если ни разу за вызов цикла не выполнялся) statement и переход к первому следующему ";" | UNE | ELSIF *)
statement}
ELSIF (* expressBoolean истинно: переход к первому следующему DO | THEN | UNTIL,
expressBoolean ложно: переход к первому следующему ";" | UNE | ELSIF | ELSE | выход из оператора *)
expressBoolean
( DO (* выполнение statementExpress и возврат к WHILEIF *)
statementExpress
| THEN (* выполнение statementExpress и выход из оператора *)
statementExpress
| UNTIL (* expressBoolean истинно: переход к первому следующему THEN | выход из оператора,
expressBoolean ложно: возврат к WHILEIF *)
expressBoolean
[THEN (* выполнение statementExpress и выход из оператора *)
statementExpress])}
[ ELSE (* выполнение statementExpress и выход из оператора *)
statementExpress]
END
Здесь присутствуют длинные переходы между сегментами оператора, а это не самый приемлемый вариант цикла. Классический случай, когда есть только переходы: к соседнему следующему сегменту, к сегменту возврата (единственному на оператор), и выход из оператора. Не рассматриваем случай структурного выхода по метке или по метке ошибки. Притом, у каждого типа сегмента может быть только один или два вида перехода, фиксированных для него. Два вида перехода у сегмента возможны, когда у оператора имеются скрытые флаги-переменные.
Также, у оператора может быть множество параметров-выражений, задающих своими значениями значения скрытых параметров-переменных и параметров-констант оператора.
Параметры-составные имена у операторов (WITH, :=), и у вызовов процедур тоже, плохи тем, что при той же текстовой записи обозначают как адреса объектов, так и их значения.
Параметры-имена у некоторых операторов (FOR) реализованы неправильно из-за модификации области видимости объектов и пространства имен.
Параметры-типы у операторов (WITH) идеологически неприемлемы, ибо типовым значениям вообще нечего делать в операторной области (тела модулей и процедур), то есть в том числе и в выражениях и в составных именах, типы объектов при вызове (загрузке) процедур и модулей должны быть фиксированы в пределах каждой области видимости.