«Pergam turbare porro: ita haec res postulat»
«Буду продолжать свои выдумки – этого требует дело»
(Плавт. "Приведение", пер. с. лат.)
В двух первых номерах журнала «КомпьютерПресс» были помещены статьи, знакомящие читателя с новым продуктом фирмы Borland Inc. – с седьмой версией языка Pascal, и которые можно считать "взглядом изнутри".
Автор этой статьи по образованию химик, а по сфере приложения сил – преподаватель ВУЗ'а. Отсюда у статьи и такое название – см. выше.
Попробуем на простом примере доказать, что ввод в Turbo Pascal 7.0 (далее TP 0.7) процедур break и continue – это только полшага в сторону повышения гибкости управляющих конструкций этого языка программирования.
На листинге 1 приведена QBasic-программа поиска корня алгебраического уравнения методом Ньютона (касательных). Почему мы начали с QBasic'а, хотя статья посвящена Pascal'ю. Дело в том, что язык QBasic кроме традиционной тройки циклов (цикл с предпроверкой, цикл с постпроверкой, цикл с параметром) имеет и цикл с выходом из середины: DO [...] IF ... THEN [...] EXIT DO [...] LOOP. Эта конструкция, наряду с другими преимуществами, о которых будет сказано ниже, позволяет реализовывать алгоритмы в их естественной последовательности. Так в программе 1 объявляются и создаются функции пользователя (анализируемое уравнение и его производная), запрашиваются значение начального приближения к корню и значение погрешности. После этого организуется цикл, но не традиционный, а с выходом из середины. В цикле, следуя естественному порядку алгоритма Ньютона, рассчитывается новое приближение к корню (X1) и, если оно отстает от предыдущего не более чем на величину заданной погрешности, то задача считается решенной (EXIT DO). Если нет, то ведется подготовка к новому приближению (X1=X2), а цикл повторяется.
При реализации на языке Pascal этот несложный алгоритм обрастает "архитектурными излишествами", т.к. его приходится "запихивать" в прокрустово ложе либо цикла while, либо цикла do. Цикл while считается "главным". С него и начнем – см. листинг 2.
Цикл с предпроверкой требует, чтобы булевое выражение заголовка было определено еще до входа в цикл, а этого нет при поиске корня методом Ньютона. Приходится до входа (и для входа) в цикл писать X2:=X1+2*Eps. Подобным образом иногда лгут детям (а машина – тоже дитя), заставляя их что-то делать или что-то не делать. Строку X2:=X1+2*Eps можно уподобить стартеру двигателя внутреннего сгорания, работающего, кстати, как и программа 2 циклически. Вот какими аллегориями – дитя, двигатель – обросла наша простенькая программа из-за того, что на языке Pascal нет цикла с выходом из середины. Можно отметить и другую ненатуральность программы 2 – "постановку телеги впереди лошади": в цикле сначала приходится готовиться к новому приближению, хотя еще не ясно, понадобиться оно или нет, а только потом проводить его. Этот же недостаток перекочевал и в Pascal-программу 3. В ней от обманного "финта" X2:=X1+2*Eps удалось избавиться за счет замены "главного" цикла while на "второстепенный", но более подходящий для данного случая цикл do. Однако вылезло новое "шило из мешка" – обманывать приходиться уже не компьютер, а самого пользователя, запрашивая у него значение X1, а засылая его (значение) в переменную X2.
Процедура break, введенная в седьмую версию Turbo Pascal'я, призвана вернуть программам 2 и 3 их естественность, но...
QBasic-конструкция DO ... LOOP (см. листинг 1) на листинге 4 превратилась в Pascal-конструкцию (версия TP 7.0) repeat ... if ... then break; ... until 1=2, окончание которой на латинский язык можно перевести как "ad calendas greaces" – "до греческих календ". Автор с замиранием сердца пытался в программе 4 убрать сюрреалистическое равенство "1=2", ставив точку с запятой сразу за ключевым словом until, но компилятор при прогонке программы стал "ругаться" и требовать булевого выражения. Пришлось как и в программах 2 и 3 идти ну не на обман, так на натяжку: "выполняй, пока рак на горе не свистнет".
История с вводом в Pascal функции break подтверждает старую истину о том, что "нет ничего практичнее хорошей теории". И вот почему.
Вышеприведенный анализ циклов на языке Pascal имеет не только сугубо практический, но и чисто теоретический аспект. Как известно, набор управляющих конструкций этого языка ведет свою родословную от основной структурной теоремы Э.Дейкстры, объявившей войну меткам: "Алгоритм любой сложности можно реализовать, используя только цикл while и альтернативу". Автор затратил уйму времени и сил в поисках статьи или книги с доказательством этой теоремы, но так и не нашел его. А вот показать, что эта теорема не верна, можно в два счета – см. листинги 5 и 6. Эти программы решают уже известную нам задачу о корне алгебраического уравнения, но другим методом – методом половинного деления. Его алгоритм – это цикл приближения к корню с вложенной альтернативой (т.е. простейшая иллюстрация теоремы Дейкстры): если корень правее центра интервала A-B, то к нему (к центру) подтягивается левый край (A:=X), нет – правый (B:=X). В программе 5 альтернатива (if ... then A := X else B := X;) заменена на два цикла while, операторы тела которых попеременно выполняются либо раз, либо ни разу. В аналогичной QBasic-программе (листинг 6) также обошлись без альтернативы. Более того, удалось избавиться и от булевой переменной-диспетчера Flag, заменив два цикла while на один цикл, но с двумя выходами из середины – с одним условным, а вторым безусловным.
Весь фокус программ 5 и 6 в том, что альтернатива – это средство ускоренного "путешествия" по алгоритму только в одну сторону (сверху вниз и слева направо, если смотреть на листинг), а цикл – в обе. Отсюда и ненужность (в теоретическом плане, конечно, а не в практическом) альтернативы. Цикл же DO [...] IF ... THEN [...] EXIT DO [...] LOOP можно считать гибридом цикла и альтернативы.
Доказательством теоремы Э.Дейкстры может служить факт, что до сих пор не было случая, когда задуманный алгоритм нельзя было бы реализовать, используя только цикл while и альтернативу. Если альтернативу исключить, то основная структурная теорема должна звучать так: "Алгоритм любой сложности можно реализовать, используя только циклы (цикл repeat ... if ... then ... break; [if ... then ... break;],,, ... until)". Вот это-то теоретическое положение вводом процедуры break и подсовывает задним числом язык Pascal под свой фундамент. Обращаю внимание на несовершенную форму глагола – "подсовывает", а не "подсунул" – цикл с выходом из середины на языке TP 7.0 осуществим либо через насилие над циклом do (листинг 4), либо над циклом while (листинг 5). В первом случае приходится лгать (1=2 – см. программу 4), а во втором наоборот – писать в заголовке цикла какую-нибудь тривиальную истину – "Волга впадает в Каспийское море" или "2*2=4", как в программе 5.
Теорему Э.Дейкстры следует "понизить в звании" и называть леммой, т.е. вспомогательной теоремой, служащей для доказательства основной.
А теперь зададимся почти гамлетовским вопросом: "Быть или не быть в Pascal-программах еще каким-либо операторам между ключевым словом then и новой процедурой break ?". На языке QBasic, как видно из программы 6, покидая цикл можно, сказать "слова прощания" – A=X или B=X, что существенно повышает гибкость этой структурной управляющей конструкции. В Pascal-программе вызов процедура break может следовать либо сразу за ключевым словом then (или за ключевым словом else), либо находиться перед словом end внутри составного оператора. Так, например, будет выглядеть альтернатива программы 6, если ее перевести на Pascal:
repeat
if (Ya * Y(X) > 0) then begin A:=X; break; end;
begin B:=X; break; end;
until 1=1;
Если процедура break находится внутри составного оператора, то она превращается в тривиальный переход к метке и ее стоило бы по-честному назвать goto end. Но если уж быть до конца справедливым, то все ключевые слова Pascal'я, формирующие циклы и альтернативы, следует считать завуалированными метками и переходами к меткам, от которых программист открещивается, перепоручая возню с ними компилятору. Здесь мы вернулись к спорам, бушевавшим лет 30 назад. Процедура break, введенная в TP 7.0, дала нам повод напомнить о них. О процедуре continue автор в статье умалчивает, не найдя более-менее подходящего примера, оправдывающего ее ввод в Pascal. Правда, при поиске автора расхолаживал тот факт, что в Qbasic такой процедуры (оператора) нет. А уж язык BASIC никогда не полениться перенять удачные находки своих "коллег".
Если читатель еще не совсем запутался, то вот какую "ясность" в проблему минимального числа управляющих конструкций алгоритмов (суть основной структурной теоремы Э.Дейкстры) внесла переведенная с английского и выпущенная в 1989 г. издательством "Мир" книга "Языки программирования Ада, Си и Паскаль. Сравнение и оценка". В ней на с. 72 и 73 сказано: "В соответствии с генеалогией управляющих структур <...> циклы с использованием оператора завершения Break и оператора продолжения Continue относятся к классу DREC1. (Такую аббревиатуру европеец не мог придумать: a dreg по-английски "отбросы"; die Dreck по-немецки "грязь", "дерьмо"). Теоремы, доказанные Р.Косараю (точно, не европеец – японец), показывают, что управляющие структуры, относящиеся к классу DREC1, не могут быть эмулированы с помощью управляющих структур, относящихся к классу D (название этого класса образовано от первой буквы фамилии Э. Дийкстры ─ автора основной структурной теоремы), которые составляются из произвольного числа условных операторов If, операторов цикла While и их конкатенаций. На самом деле управляющие структуры, относящиеся к классу DRECi (имеющие в своем составе оператор выхода Exit(i) (или break как на TP 7.0), обеспечивающий выход на i уровней вверх, и оператор Cycle(i) (или continue), обеспечивающий выполнение цикла на i-м уровне вложенности, как, например, в языке Блисс), являются более мощными, чем управляющие структуры, относящиеся к классу DRECi-1. <...> Однако программы, содержащие циклы с использованием операторов Exit и Cycle, оказываются намного сложнее для понимания. Анализ показывает, что необходимость использования оператора Exit возникает крайне редко. Необходимость наличия управляющих структур более высокого уровня, чем управляющие структуры класса D до сих пор не доказана".
Вот так-то. Начали "за упокой" основной структурной теоремы, а кончили ─ "за здравие".
Но вернемся к практическим заботам.
Естественно, и без процедуры break можно писать циклы с любым числом выходов из середины и со "словами прощания". Мы уже разобрали реализацию цикла с выходом из середины через цикл while (листинг 2) и цикл do (листинг 3). Кроме того, часть операторов циклов while, do или for можно обойти, вставив в тело цикла альтернативу с одним плечом. Если выходов из цикла много, то его тело можно вынести в отдельную процедуру, разбросав в ней процедуры exit, которые в теле другой процедуры подобны процедуре break. Кстати, непонятно, почему break и exit в отличие, например, от if или while не удостоились звания ключевых слов. Что это еще за деление на чистых и нечистых! Это тем более непонятно, если принять во внимание, что ключевые слова do, then, else и begin вообще нельзя считать ключевыми словами, так как они не несут никакой смысловой нагрузки, выполняя чисто декоративные функции комментариев, украшающих программу и делающих ее более "читабельной". С другой стороны ключевому слову end уже давно пора "развалиться на куски" – на ключевые слова end while, end for, end then, end else и т.д., что исключит перегруженность текстов программ комментариями типа {end while}, {end for} и т.д. Но на изменение списка ключевых слов языка Pascal наложено табу, нарушать которое позволительно только по "большим праздникам", например, в момент ввода в Pascal элементов объектно-ориентированного программирования (Turbo Pascal 5.5). Возведение же процедур exit и break в ранг ключевых слов было бы не праздником, а печальным событием, напоминающем о том, что Pascal базируется на дефектной основной структурной теореме.
Теперь взглянем на седьмую версию Pascal'я глазами программиста прикладника, который, конечно, очень далек от теоретических споров тем более тридцатилетней давности, и закончим статью о структурных управляющих конструкциях Pascal'я структурным анализом ее заголовка: "взгляд со стороны".
1. Почему со стороны?
1.1. Автор по образованию химик см. начало статьи.
1.2. Автор для решения своих прикладных задач использует BASIC (Quick- и Q-версии от Microsoft).
1.3. Автор пытается перейти на Pascal и C, но... "рад бы в рай, да грехи не пускают". Чьи грехи – см. п. 2.4.
2. Какой взгляд?
2.1. Пристрастный, т.к.:
2.1.1. См. п. 1.2.
2.1.2. Статей, просто информирующих о новинках Turbo Pascal'я и так достаточно.
2.2. Поверхностный, т.к.:
2.2.1. См. п. 1.1.
2.2.2. Автор не работал ни с документацией, ни с дистрибутивом TP 7.0, а только с его пиратской копией, т.к.:
2.2.2.1. У него нет 150 US$ на покупку TP 7.0.
2.2.2.2. Автора можно отучить "химичить" (см. п. 1.1) с программными продуктами, только установив ему оклад, порядок которого обозначен в п. 2.2.2.1.
2.3. Объективный, т.к. автор (слава Богу, а, может быть, к сожалению – см. п. 2.2.2.1) не входит в круг лиц, который у нас условно называют компьютерной мафией, кормящейся около российских представительств западных фирм.
2.4. Конструктивный, т.к. статья завершается списком необходимых доработок Pascal'я для того, чтобы к нему повернулись программисты-прикладники:
2.4.1. Наличие цикла с выходом из середины.
2.4.2. Наличие оператора возведения в степень.
2.4.3. Возможность организации цикла с действительным параметром, а не только с целочисленным, имеющим шаг либо плюс 1, либо минус 1.
2.4.4. Возможность использования в именах переменных (функций и процедур) спецсимволов – греческих и русских букв, плюсов-минусов, индексов и т.д. как, например, на языке FRED, входящем в состав интегрированного пакета Framework тоже фирмы Borland.
2.4.5. Перенос функций и процедур "в зазеркалье" основной программы, как это сделано на языке QBasic, где после вызова программы на дисплее появляется только текст основной программы, а все процедуры и функции "не мешаются под ногами" и при необходимости вызываются на дисплей по одной, а не все сразу.
2.4.6. Разработка укороченной версии языка TP (условно назовем ее TPascal) и включение ее в состав операционной системы компьютера в целях исключения работы с пиратскими копиями – см. п.п. 2.2.2.1 и 2.2.2.2. Пример – укороченная версия языка QuickBASIC под названием QBasic, входящая в состав MS-DOS 5.0.
2.4.7. Включение в состав среды программирования полноценного текстового процессора, позволяющего в текстах программ менять шрифт, цвет, отмечать индексы и степени.
2.4.8. Объединение операторов write и read в оператор input с комментарием.
2.4.9. Ввод нормального оператора print using.
2.4.10. Исключение из текстов Pascal-программ точек с запятой и ритуальной финишной точки. Неужели компилятор без помощи программиста не может разобраться, что на этих местах ничего другого стоять не может.
2.4.11. Запрет повторного открытия одноименного файла из одноименного каталога. |