Э. Дейкстра Дисциплина программирования. Предисловие редактора перевода
Скачать 1.14 Mb.
|
10 Э. Дейкстра. ”Дисциплина программирования” программированию на PL/1 можно найти настойчивый совет избегать обращений к процедурам, на- сколько это возможно, "потому что они делают программу столь неэффективной". В свете того, что процедуры в языке PL/1 являются одним из основных средств oписания структуры, этот совет выгля- дит ужасно, настолько ужасно, что вряд ли можно называть упомянутый текст "учебным". Если вы убеждены в полезности понятия процедуры и соприкасаетесь с приложениями, где дополнительные затраты на аппарат процедур обходятся слишком дорого, то порицайте эти неподходящие приложе- ния, а не возводите их на уровень стандартов. Равновесие и в самом деле надлежит восстановить!) Я рассматриваю язык программирования преимущественно как средство для описания (потен- циально весьма сложных) абстрактных конструкций. Как показано в главе "Абстракция исполне- ния", первейшим достоинством алгоритма является потенциальная компактность рассуждений, на которых может основываться наше проникновение в его сущность. Как только эта компактность потеряна, алгоритм в значительной мере теряет "право на существование", и поэтому мы будем стре- миться к сохранению такой компактности. Соответственно и наш выбор языка программирования будет подчинен той же цели. Э. Дейкстра. ”Дисциплина программирования” 11 2. СОСТОЯНИЯ И ИХ ХАРАКТЕРИСТИКА В течение многих столетий человек оперирует натуральными числами. Мне представляется, что в доисторические времена, когда перед нашими предками впервые забрезжило понятие числа, они изобретали индивидуальные имена для каждого числа, на которое у них обнаруживалась потребность сослаться. Им приходилось иметь имена для чисел точно так же, как мы имеем имена "один, два, три, четыре и так далее". Это поистине "имена" в том смысле, что при исследовании последовательности "один, два, три" никакое правило не поможет нам умозаключить, что следующим именем будет "четыре". Вы, ко- нечно же, должны это знать. (В том возрасте, когда я вполне удовлетворительно умел считать — по-голландски,— мне пришлось учиться счету по-английски и во время экзамена самое изощренное знание слов "семь" и "девять" не смогло помочь мне установить, как писать слово "восемь", не говоря уж о том, как произносить его.) Очевидно, что такое несистематизированное разнообразие позволяет нам выделять индивидуаль- но только весьма ограниченное количество различных чисел. Чтобы избежать подобного ограниче- ния, каждый язык в цивилизованном мире вводит (более или менее) систематизированное именова- ние натуральных чисел, и обучение счету в значительной степени сводится к обнаружению системы, лежащей в основе этого именования. Когда ребенок учится считать до тысячи, ему не приходится заучивать наизусть эту тысячу имен (в их точном порядке!); он руководствуется определенными пра- вилами: наступает момент, когда ребенок узнает, как переходить от любого числа к следующему, например от "четыреста двадцать девять" к "четыреста тридцать". Легкость обращения с числами сильно зависит от выбранной нами для них систематизации. Гораздо труднее установить, что дюжина дюжин = гросс одиннадцать плюс двенадцать = двадцать три XLV II + IV = LI чем установить, что 12 × 12 = 144 11 + 12 = 23 47 + 4 = 51 так как последние три ответа можно получить, применив простой набор правил, которыми может руководствоваться любой восьмилетний ребенок. При механических манипуляциях над числами преимущества десятичной системы счисления проявляются гораздо более отчетливо. Уже в течение столетий мы располагаем механическими сум- маторами, показывающими ответ в окошке, позади которого имеется несколько колес с десятью различными пoлoжeниями, причем каждое колесо в любом своем положении показывает одну деся- тичную цифру. (Теперь уже уже не является проблемой показать "00000019", прибавить 4 и затем показать "00000023"; было бы проблемой — по крайней мере если пользоваться чисто механическими средствами — показать вместо этого "девятнадцать" и "двадцать три".) Существенное свойство такого колеса состоит в том, что оно обладает десятью различными устой- чивыми положениями. Терминологически это выражается разными способами. Например, колесо называется "переменной с десятью значениями", и если мы хотим большей точности, то даже пере- числяем эти значения: от 0 до 9. Здесь каждое "положение" колеса отождествляется со "значением" переменной. Колесо называется "переменной", потому что, хотя его положения и устойчивы, коле- со может повернуться в другое положение: "значение" может измениться. (Я вынужден отметить, что этот термин неудачен по крайней мере в двух отношениях. Во-первых, такое колесо, которое (почти) всегда находится в одном из своих десяти положений и, следовательно, (почти) всегда пред- ставляет некое значение, является понятием, весьма отличным от того, что математики называют "переменной", поскольку, как правило, математическая переменная вообще не представляет какого- либо конкретного значения. Если мы говорим, что для любого целого числа n утверждение n 2 ≥ 0 истинно, то здесь n является переменной совсем другого рода. Во-вторых, в нашем контексте мы используем термин "переменная" для того, что существует во времени и чье значение, если его не трогать, остается постоянным! Термин "изменяемая константа" подошел бы лучше, но мы не станем вводить его, а будем придерживаться прочно установившейся традиции.) Другой способ терминологически отразить сущность такого колеса, которое (почти) всегда нахо- дится в одном из десяти различных положений или "состояний", состоит в том, чтобы поставить этому колесу в соответствие "пространство состояний из десяти точек". Здесь каждое состояние (положе- ние) связывается с "точкой", а собрание этих точек называется — в соответствии с математической 12 Э. Дейкстра. ”Дисциплина программирования” традицией — пространством или, если мы хотим большей точности, "пространством состояний". Вместо того чтобы говорить, что переменная обладает (почти) всегда одним из своих возможных значений, мы можем теперь выразить то же, сказав, что система, состоящая из единственной пе- ременной, находится (почти) всегда в одной из точек своего пространства состояний. Пространство состояний описывает степень свободы системы; больше ей некуда переходить. Отвлечемся теперь от отдельно взятого колеса и сосредоточим наше внимание на регистре с во- семью такими колесами в строке. Поскольку каждое из этих колес находится в одном из десяти различных состояний, этот регистр, рассмотренный как целое, находится в одном из 100 000 000 воз- можных различных состояний, каждое из которых естественно идентифицируется числом (а точнее, строкой из восьми цифр), показываемым в окошке. Если заданы состояния всех колес, то состояние регистра в целом однозначно определено; и обрат- но, по любому состоянию регистра в целом однозначно определяется состояние каждого отдельного колеса. В этом случае мы говорим (в предыдущей главе нами уже использован этот термин), что по- лучаем (или строим) пространство состояний регистра в целом, формируя "декартово произведение" пространств состояний восьми отдельных колес. Общее число точек в этом пространстве состояний яв- ляется произведением чисел точек в тех пространствах состояний, из которых оно построено (именно поэтому оно называется декартовым произведением). В зависимости от того, что нас интересует в этом предмете, мы либо рассматриваем такой ре- гистр как единую переменную с 10 8 различными возможными значениями, либо как составную переменную, составленную из восьми различных переменных, называемых "колесами", с десятью значениями. Если мы интересуемся только показываемыми значениями, то будем, по-видимому, рас- сматривать регистр как неделимое понятие, тогда как инженер-эксплуатационник, которому нужно заменить колесо со сломанным зубцом, будет, конечно рассматривать регистр как составной объект. Мы наблюдали другой пример построения пространства состояний как декартова произведения более мелких пространств состояний, когда обсуждали алгоритм Евклида и обнаружили, что положе- ние фишки где-то на листе можно полностью идентифицировать с помощью двух полуфишек, каждая из которых находится где-то на своей оси, т. е. с помощью комбинации (а точнее, упорядоченной па- ры) двух переменых "x" и "y". (Идея идентификации положения точки на плоскости значениями ее координат x и y идет от Декарта, который разработал аналитическую геометрию и в честь которого названо декартово произведение.) Фишка на листе была введена для демонстрации того факта, что развивающийся вычислительный процесс — такой, как выполнение алгоритма Евклида,— можно рассматривать как систему, движущуюся по своему пространству состояний. В соответствии с этой метафорой начальное состояние именуется также "исходной точкой". В этой книге мы будем преимущественно — а может быть, даже исключительно — заниматься системами, пространства состояний которых будут в конечном итоге рассматриваться как некие де- картовы произведения. Разумеется, не следует усматривать в этом мое мнение, будто бы пространства состояний, построенные с помощью декартовых произведений, являются общим и окончательным решением всех наших проблем; я отлично знаю, что это не так. Из дальнейшего станет очевидным, почему они заслуживают столь большого нашего внимания и в то же время почему это понятие играет столь главенствующую роль во многих языках программирования. Прежде чем продолжить рассуждения, отмечу одну проблему, с которой нам придется столкнуть- ся. Когда мы oбразуем пространство состояний, строя декартово произведение, то вовсе не обязатель- но, что нам окажутся полезными все его точки. Типичным примером этому является характеристика дней данного года с помощью пары (месяц, день), где "месяц" — переменная с 12 значениями (от "янв" до "дек"), а "день" — переменная с 31 значениями (от 1 до 31). В этом случае мы образуем пространство состояний из 372 точек, тогда как никакой год не содержит более чем 366 дней. Что нам делать, скажем, с парой (июн, 31)? Либо мы запрещаем ее, вводя понятие "невозможных дат" и тем самым создавая в некотором смысле возможность внутренних противоречий в cистеме, либо мы разрешаем ее как другой вариант имени для одного из "истинных" дней, например, приравнивая ее к (июл,1). Феномен "неиспользуемых точек пространства состояний" возникает всякий раз, ко- гда число различных возможных значений, которые мы желаем распознавать, оказывается простым числом. Систематизация, которая автоматически вводится, когда мы строим пространство состояний как декартово произведение, позволяет нам идентифицировать отдельно взятую точку. Например, я могу утверждать, что мой день рождения — это (май, 11). Однако благодаря Декарту нам известен теперь и другой способ фиксации этого факта: мой день рождения приходится на ту дату (месяц, день), Э. Дейкстра. ”Дисциплина программирования” 13 которая соответствует решению уравнения 1 (месяц = май) and (день = 11) Приведенное выше уравнение имеет только одно решение и поэтому представляет собой несколь- ко усложненный способ указания этого единственного дня в году. Однако преимущество использова- ния уравнения состоит в том, что оно позволяет нам охарактеризовать множество всех его решений, и такое множество может содержать значительно больше чем одну точку. Тривиальным примером может служить определение рождества: (месяц = дек) and ((день = 25) or (день = 26)) Более примечательным примером является определение множества дат, в которые выплачивается мое ежемесячное жалованье: (день = 23) и разумеется, это гораздо более компактное описание, чем перечисление вида "(янв, 23), (фев, 23), (мар, 23)" и т. д. Из сказанного выше явствует, что степень легкости, с которой мы используем такие уравнения для характеристики множеств состояний, зависит от того, насколько множества, которые мы хотим охарактеризовать, "соответствуют" структуре пространства состояний, т. е. "соответствуют" введен- ной системе координат. Например, в рассмотренной выше системе координат было бы несколько неудобно охарактеризовать множество дней, которые приходятся на тот же день недели, что и (янв, 1). Во многих случаях программисту приходится при решении задач вводить пространства состоя- ний с подходящими для его целей системами координат; причем дополнительные требования часто вынуждают его вводить также пространства состояний, в которых число точек во много раз больше, чем число различных возможных значений, которые он должен распознавать. Другой пример использования уравнения для характеристики множества состояний встречался в нашем описании картонной машины для вычисления НОД(X, Y ), где x = y характеризует все точки того, что мы назвали "линией ответа"; это множество конечных состоя- ний, т. е. вычисление прекращается тогда и только тогда, когда достигнуто какое-нибудь состояние, удовлетворяющее уравнению x = y. Помимо координат пространства состояний, т. е. переменных, в терминах значений которых выражается ход вычислительного процесса, мы встречали в наших уравнениях константы (такие, как "май" или "23"). Кроме того, можно пользоваться так называемыми "свободными переменны- ми", которые можно представить себе как "неспецифицированные константы". Мы применяем их специально для обозначения связей различных состояний, которые могут встречаться на последова- тельных шагах одного и того же вычислительного процесса. Например, во время некоего конкретного выполнения алгоритма Евклида с начальной точкой (X, Y ) все состояния (x, y) будут удовлетворять формуле НОД(x, y) = НОД(X, Y ) and 0 < x ≤ X and 0 < y ≤ Y Здесь X и Y не являются такими переменными, как x и y. Они представляют собой "их начальные значения". Это константы с точки зрения конкретного вычисления, но они не специфицированы в том смысле, что мы могли бы запустить алгоритм Евклида с любой точкой сетки в качестве начального положения нашей фишки. В заключение некоторые терминологические замечания. Я буду называть такие уравнения "усло- виями" или "предикатами". (Я мог бы, а возможно, и должен бы различать эти понятия, зарезерви- ровав термин "предикат" для формального выражения, обозначающего "условие". Тогда мы имели бы возможность сказать, например, что два разных предиката "x = y" и "y = x" обозначают одно и то же условие. Однако, зная себя, я не надеюсь преуспеть в такой манере изложения.) Я буду считать синонимичными такие выражения, как "состояние, для которого предикат истинен", "состояние, которое удовлетворяет условию", "состояние, в котором условие удовлетворяется", "состояние, в ко- тором условие справедливо" и т. д. Если система заведомо достигнет состояния, удовлетворяющего условию P , то мы будем говорить, что система заведомо "обеспечит истинность P ". 1 Здесь и далее в математических формулах используются для обозначения логических операций символы and (конъюнкция), or (дизъюнкция) и non (отрицание).— Прим. перев. 14 Э. Дейкстра. ”Дисциплина программирования” Предполагается, что всякий предикат определен в каждой точке рассматриваемого пространства состояний: в каждой точке значением предиката является либо "истина", либо "ложь", и предикат служит для характеристики множества всех точек, для которых (или в которых) этот предикат истинен. Мы будем называть два предиката P и Q равными (формально "P = Q"), если они обозначают одно и то же условие, т.е характеризуют одно и то же множество состояний. Два предиката будут играть особую роль, и мы резервируем для них имена "T " и "F ". T —это предикат, который истинен во всех точках рассматриваемого пространства состояний; ему соответствует полное множество состояний. F — предикат, который ложен во всех точках пространства состояний; он соответствует пустому множеству. Э. Дейкстра. ”Дисциплина программирования” 15 3 ХАРАКТЕРИСТИКА СЕМАНТИКИ Нас интересуют преимущественно такие системы, которые, приступив к работе в некоем "на- чальном состоянии", завершат ее в каком-то "конечном состоянии", которое, как правило, зависит от выбора начального состояния. Этот подход несколько отличается от концепции автомата с ко- нечным числом состояний, который, с одной стороны, воспринимает поток входных символов, а с другой стороны, порождает поток выходных символов. Чтобы перевести это в нашу схему, мы долж- ны предположить, что значение входа (т. е. аргумент) oтражается в выборе начального состояния и что значение выхода (т. е. ответ) отражается в выборе конечного состояния. Наш подход избавляет нас от всевозможных непринципиальных сложностей. Первая часть этой главы посвящена почти исключительно так называемым "детерминированным машинам", тогда как во второй части (которую можно пропустить при первом чтении) речь идет о так называемых "недетерминированных машинах". Различие между этими двумя понятиями состоит в том, что для детерминированной машины событие, которое произойдет после запуска конструкции, полностью определяется ее начальным состоянием. Если она запускается дважды в одинаковых на- чальных состояниях, то произойдут одинаковые события: поведение детерминированной машины полностью воспроизводимо. Для недетерминированной машины, напротив, запуск в заданном на- чальном состоянии приведет к какому-то одному событию из класса возможных событий; начальное состояние только фиксирует этот класс в целом. Теперь я предполагаю, что проектирование такой системы является целенаправленной деятель- ностью, т. е. мы желаем достичь чего-то с помощью этой системы. Например, если мы хотим создать машину, способную вычислять наибольший общий делитель, то можем потребовать, чтобы конечное состояние удовлетворяло условию x = НОД(X, Y ) (1) В рассмотренной ранее машине мы будем иметь также y = НОД(X, Y ), потому что игра заканчивается при x = y, но это отнюдь не часть наших требований, когда мы решаем принять конечное значение x в качестве ответа. Мы называем условие (1) (желаемым) "постусловием" — "пост", потому что оно налагается на то состояние, в котором система должна оказаться после своей работы. Заметим, что постусловие может удовлетворяться многими возможными состояниями. В таком случае мы естественно считаем каждое из них одинаково удовлетворительным, так что нет оснований требовать, чтобы конечное состояние было однозначной функцией от начального состояния. (Читатель увидит из дальнейшего, что именно здесь проявляется потенциальная полезность недетерминированного устройства.) Для того чтобы использовать эту машину, когда мы хотим получить от нее ответ (например, хотим "чтобы она достигла конечного состояния, удовлетворяющего постусловию (1) для заданного набора значений X и Y "), нам желательно знать множество соответствующих начальных состояний, а более точно, множество таких начальных состояний, при которых запуск обязательно приведет к событию правильного завершения причем система останется в конечном состоянии, удовлетворяющем посту- словию. Если мы можем привести систему без вычислительных усилий в одно из таких состояний, то мы уже знаем, как использовать эту систему для получения желаемого ответа. Приведем пример для евклидовой игры на картоне: мы можем гарантировать конечное состояние, удовлетворяющее постусловию (1) для любого начального состояния, удовлетворяющего условию НОД(x, y) = НОД(X, Y ) and 0 < x ≤ 500 and 0 < y ≤ 500 (2) (Верхние границы добавлены, чтобы учесть ограниченный размер листа картона. Если мы начинаем с парой (X, Y ), такой, что НОД(X, Y ) = 713, то не существует пары (x, y), удовлетворяющей условию (2), т. е. для таких значений X и Y условие (2) сводится к предикату F , а это означает, что рассматриваемая машина не может быть использована для вычисления НОД(X, Y ) применительно к этой паре значений X и Y ). При многих комбинациях (X, Y ) многие состояния удовлетворяют условию (2). В случае 0 < X ≤ 500 и 0 < Y ≤ 500 тривиальным выбором является x = X и y = Y . Этот выбор может быть произве- ден без какого-либо вычисления функции НОД и даже без учета того факта, что это симметричная функция от своих аргументов. Условие, характеризующее множество всех начальных состояний, при которых запуск обяза- тельно приведет к событию правильного завершения, причем система останется в конечном состо- янии, удовлетворяющем заданному постусловию, называется "слабейшим предусловием, соответ- ствующим этому постусловию". (Мы называем его "слабейшим", поскольку, чем слабее условие, тем больше состояний удовлетворяют емy, а мы стремимся здесь охарактеризовать все возможные начальные состояния, которые неизбежно приведут к желаемому конечному состоянию.) |