OberonCore
https://forum.oberoncore.ru/

Убираю LOOP'ы
https://forum.oberoncore.ru/viewtopic.php?f=82&t=2547
Страница 1 из 4

Автор:  Александр Ильин [ Понедельник, 12 Апрель, 2010 06:27 ]
Заголовок сообщения:  Убираю LOOP'ы

Сначала ломал голову над странными LOOP'ами. Потом обнаружил повторяющуюся схему:
Код:
LOOP
   IF A THEN
      EXIT;
   ELSIF B THEN
      OneShotCode; (* здесь может быть много строк кода *)
      EXIT;
   END;
   Step; (* здесь тоже иногда больше одной строки *)
END;
Понял, что это всё преобразуется в простой и понятный линейный поиск:
Код:
WHILE ~A & ~B DO
   Step;
END;
IF ~A THEN
   OneShotCode;
END;
В такой записи код более компактен как по числу строк, так и по уровню отступа. После преобразования понял, что было самым странным. То, что код OneShotCode находится внутри цикла, но выполняется только один раз. :twisted:

Автор:  Александр Ильин [ Понедельник, 12 Апрель, 2010 06:29 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Пример:
Код:
   LOOP
      IF tmp = NIL THEN
         EXIT;
      ELSIF tmp = li THEN
         IF prev = NIL THEN
            w.firstLine := li.next;
         ELSE
            prev.next := li.next;
            LOOP
               tmp := tmp.next;
               IF tmp = NIL THEN EXIT END;
               DEC (tmp.idx);
            END; (* loop *)
         END; (* if prev *)
         EXIT;
      END; (* if tmp *)
      INC (py, tmp.height); prev := tmp; tmp := tmp.next;
   END; (* loop *)
Код:
   WHILE (tmp # NIL) & (tmp # li) DO
      INC (py, tmp.height); prev := tmp; tmp := tmp.next;
   END;
   IF tmp # NIL THEN
      IF prev = NIL THEN
         w.firstLine := li.next;
      ELSE
         prev.next := li.next;
         tmp := tmp.next;
         WHILE tmp # NIL DO
            DEC (tmp.idx);
            tmp := tmp.next;
         END;
      END; (* if prev *)
   END; (* if found tmp = li *)

Автор:  Александр Ильин [ Понедельник, 12 Апрель, 2010 07:38 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Ещё один пример:
Код:
LOOP
   Step;
   IF A THEN EXIT END;
   CodeBlock;
END;
Преобразуется в:
Код:
Step;
WHILE ~A DO
   CodeBlock;
   Step;
END;
Данная схема присутствует в предыдущем сообщении - см. вложенный LOOP.

Автор:  Peter Almazov [ Понедельник, 12 Апрель, 2010 07:41 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Александр Ильин писал(а):
Сначала ломал голову над странными LOOP'ами. Потом обнаружил повторяющуюся схему:
Чей код-то?

Автор:  Александр Ильин [ Понедельник, 12 Апрель, 2010 07:46 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Встречаются и тривиальные экземпляры, в которых вообще неясно, зачем было LOOP использовать:
Код:
LOOP
   IF ~ProcCall () THEN EXIT END;
   Step; (*может быть несколько строк *)
END;
Код:
WHILE ProcCall () DO
   Step; (*может быть несколько строк *)
END;

Автор:  Александр Ильин [ Понедельник, 12 Апрель, 2010 12:02 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Встретил вариант схемы из первого поста:
Код:
LOOP
   IF A THEN
      OneShotCode; (* здесь может быть много строк кода *)
      EXIT;
   ELSIF B THEN
      EXIT;
   END;
   Step; (* здесь тоже иногда больше одной строки *)
END;
Код:
WHILE ~A & ~B DO
   Step;
END;
IF A THEN
   OneShotCode;
END;
Отличие в том, что OneShotCode выполняется при условии A, а не B. Соответственно, после WHILE проверяется не ~A, а просто A.

Автор:  Info21 [ Понедельник, 12 Апрель, 2010 12:15 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Александр Ильин писал(а):
Встречаются и тривиальные экземпляры, в которых вообще неясно, зачем было LOOP использовать
Как зачем? Для крутизны :)

Автор:  Александр Ильин [ Понедельник, 12 Апрель, 2010 13:30 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Вот ещё нечитабельная схема:
Код:
VAR var: BOOLEAN;
BEGIN
   var := FALSE;
   LOOP
      SomeCode (var); (* нечто многострочное, не содержащее EXIT и RETURN и не меняющее значение var *)
      IF var OR OtherConditions () THEN
         EXIT
      END;
      var := TRUE;
   END;
Поди-разгляди, что выполняется этот цикл один - максимум два раза. Цикл не нужен. SomeCode оформляется в отдельную процедуру:
Код:
VAR dummy: BOOLEAN;

   PROCEDURE SomeCode (var: BOOLEAN): BOOLEAN;
   BEGIN
      (* нечто многострочное, не содержащее EXIT и RETURN и не меняющее значение var *)
      RETURN var OR OtherConditions ()
   END SomeCode;

BEGIN
   dummy := SomeCode (FALSE) OR SomeCode (TRUE);
Значение dummy ингорируется, оно всегда = TRUE. Если не хочется заводить переменную dummy, то можно написать так (дело вкуса):
Код:
IF SomeCode (FALSE) OR SomeCode (TRUE) THEN (* do nothing *)
END;

Здесь после устранения LOOP объём кода не уменьшается, но, на мой взгляд, повышается ясность.

Автор:  Александр Ильин [ Понедельник, 12 Апрель, 2010 14:12 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Вот ещё один странный экземпляр. Оригинал кода:
Код:
   LOOP
      IF tmp = dragLi THEN IF prev = NIL THEN w.SetFirstLine (dragLi.next) ELSE prev.next := dragLi.next END; EXIT END;
      prev := tmp; tmp := tmp.next;
   END; (* loop *)
Схема:
Код:
LOOP
   IF B THEN
      SomeCode;
      EXIT
   END;
   Step;
END;
С помощью WHILE:
Код:
WHILE ~B DO
   Step;
END;
SomeCode;
В общем-то, этот пример получается из примера первого поста подстановкой A = FALSE.

Автор:  Alexey Veselovsky [ Понедельник, 12 Апрель, 2010 15:11 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Это где такое?

Автор:  Александр Ильин [ Понедельник, 12 Апрель, 2010 19:12 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Ещё пример, на этот раз с тремя ветками IF внутри LOOP, плюс разбавлено RETURN'ом:
Код:
LOOP
   IF A THEN
      EXIT
   ELSIF B THEN
      RETURN FALSE
   ELSIF C THEN
      EXIT
   END;
   Step;
END; (* loop *)
Преобразую:
Код:
WHILE ~A & ~B & ~C DO
   Step;
END;
IF ~A & B THEN
   RETURN FALSE
END;

Автор:  Валерий Лаптев [ Понедельник, 12 Апрель, 2010 19:48 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Блестящий пример знания и использования ТЕХНИКИ программирования!

Автор:  Info21 [ Понедельник, 12 Апрель, 2010 20:48 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Валерий Лаптев писал(а):
Блестящий пример знания и использования ТЕХНИКИ программирования!
Думаю, Александр Сергеевич согласится, что техника тут где-то на уровне деления в столбик, так что блеск только на фоне общего мрака.

Этот мрак -- такой архилюбопытный антропологический феномен, что даже раздел на форуме заведен.

Автор:  Валерий Лаптев [ Понедельник, 12 Апрель, 2010 20:54 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Не... Тут ВАЖНОЕ есть: конкретные примеры плохого кода и преобразованные в хороший. Это ВАЖНО! Тем более, что когда все на уровне арифметики 1 класса, а чел вдруг демонстрирует знание таблицы умножения - это выглядит как откровение.
И кстати, прекрасный пример рефакторинга - это чтобы молодежь не возникала! Самое современное слово используем... :)

Автор:  Александр Ильин [ Понедельник, 12 Апрель, 2010 21:04 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

В одном месте согласился на REPEAT..UNTIL. Первоначальный код:
Код:
LOOP IF (Files.IsDirectory * dp.attr = {}) OR ((dp.res # '.') & (dp.res # '..')) THEN RETURN FALSE ELSIF ~FindNext (dp) THEN EXIT END END;
RETURN TRUE
(Да-да, LOOP в одну строчку.)
Схема:
Код:
LOOP
   IF Condition () THEN
      RETURN FALSE
   ELSIF ~Next () THEN
      EXIT
   END
END;
RETURN TRUE
Добавил переменную результата res: BOOLEAN и получил:
Код:
REPEAT
   res := Condition ();
UNTIL res OR ~Next ();
RETURN ~res
В принципе, можно было через WHILE, но не захотелось Condition дублировать или выносить в отдельную процедуру. Вот как мог бы выглядеть WHILE:
Код:
res := Condition ();
WHILE ~res & Next () DO
   res := Condition ();
END;
RETURN ~res

Автор:  ==== [ Понедельник, 12 Апрель, 2010 21:11 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Info21 писал(а):
Этот мрак
Вашу аргументацию не понял, точнее ее нет.

Автор:  Ярослав Романченко [ Понедельник, 12 Апрель, 2010 21:14 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Александр Ильин писал(а):
В одном месте согласился на REPEAT..UNTIL
И чем-же так страшен REPEAT..UNTIL? :mrgreen:

Автор:  Александр Ильин [ Понедельник, 12 Апрель, 2010 21:16 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Комбинированный пример:
Код:
LOOP
   p := Str.PosFrom (t, fs.dir, p);
   IF (p = Str.NotFound) OR (p = 0) THEN
      EXIT;
   ELSIF (p > 0) & (fs.dir [p - 1] = dirChar) THEN
      q := p; DEC (p);
      WHILE (p > 0) & (fs.dir [p - 1] # dirChar) DO DEC (p) END;
      Str.Delete (fs.dir, p, q - p + 3);
   END; (* if *)
   INC (p);
END; (* loop *)
Схема:
Код:
LOOP
   CodeBlock1;
   IF A THEN
      EXIT;
   ELSIF B THEN
      CodeBlock2;
   END;
   Step;
END;
Видим, что CodeBlock1 предшествует конструкции IF-EXIT, выносим его "за скобки" будущего WHILE. Не забываем продублировать после Step:
Код:
CodeBlock1;
LOOP
   IF A THEN
      EXIT;
   ELSIF B THEN
      CodeBlock2;
   END;
   Step;
   CodeBlock1;
END;
Обратим внимание, что в блоке CodeBlock2 нет EXIT'а, значит он не подходит под ранее рассмотренные схемы. Если бы EXIT был, то CodeBlock2 мы бы вынесли из WHILE и выполнили один раз после цикла. В данном же случае блок должен остаться внутри WHILE. Окончательная схема:
Код:
CodeBlock1;
WHILE ~A DO
   IF B THEN
      CodeBlock2;
   END;
   Step;
   CodeBlock1;
END;

Автор:  Александр Ильин [ Понедельник, 12 Апрель, 2010 21:50 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Alexey Veselovsky писал(а):
Это где такое?
Глобальному перетряхиванию подвергнута библиотека Amadeus-3, примеры берутся оттуда.
Info21 писал(а):
техника тут где-то на уровне деления в столбик
Тем не менее, заниматься подобным ранее мне не доводилось, так как LOOP отродясь не использовал. Публично делаю затем, чтобы дотошный читатель мог указать мне на ошибки. Кстати, составление схем уже само по себе хорошо помогает.

Автор:  Александр Ильин [ Понедельник, 12 Апрель, 2010 22:12 ]
Заголовок сообщения:  Re: Убираю LOOP'ы

Фантазия автора не перестаёт удивлять. Может быть, он прочитал Паронджанова и в голове представляет ДРАКОН-схему, когда такое пишет? Схема с разорванным IF'ом:
Код:
LOOP
   IF A THEN
      EXIT
   END;
   Step;
   IF B THEN
      OneShotCode;
      EXIT
   ELSIF C THEN
      EXIT
   END;
END;
Условие A надо поставить перед циклом и в конце его:
Код:
IF ~A THEN
   LOOP
      Step;
      IF B THEN
         OneShotCode;
         EXIT
      ELSIF C OR A THEN
         EXIT
      END
   END;
END;
Далее видим, что LOOP оказался обычным REPEAT..UNTIL'ом:
Код:
IF ~A THEN
   REPEAT
      Step;
   UNTIL B OR C OR A;
   IF B THEN
      OneShotCode;
   END;
END;
Можно записать и как WHILE:
Код:
IF ~A THEN
   Step;
   WHILE ~B & ~(C OR A) DO
      Step;
   END;
   IF B THEN
      OneShotCode;
   END;
END;

Страница 1 из 4 Часовой пояс: UTC + 3 часа
Powered by phpBB® Forum Software © phpBB Group
https://www.phpbb.com/