Дискуссионный математический форумМатематический форум

Математический форум Math Help Planet

Обсуждение и решение задач по математике, физике, химии, экономике

Теоретический раздел
Часовой пояс: UTC + 4 часа [ Летнее время ]
MathHelpPlanet.com RSS-лента Математического форума

Часовой пояс: UTC + 4 часа [ Летнее время ]


Математическая логика и языки программирования

Математическая логика и языки программирования


Образно выражаясь, можно сказать, что компьютер состоит из материальной части и математического (программного) обеспечения, или, используя профессиональную лексику, из "железа" и "обуви". И к тому, и к другому имеет самое непосредственное отношение математическая логика, ни первое, ни второе без математической логики обойтись не могут. Ранее было рассмотрено применение математической логики к релейно-контактным (переключательным) схемам, являющимся неотъемлемой составной частью современного компьютера. Часть настоящей главы также посвящена вопросам взаимодействия математической логики и компьютеров. Так, в § 38 рассказывается о применении математической логики к языкам программирования и к самому процессу программирования и получающимся в результате этого программам. В § 39 дается характеристика обратного процесса — применению компьютеров для поиска доказательств теорем математической логики и других математических дисциплин. Значительное внимание уделено методу резолюций для доказательства теорем в исчислениях высказываний и предикатов. В § 40 кратко описывается язык ПРОЛОГ — принципиально новый язык программирования, выросший непосредственно из математической логики (логики- предикатов) и призванный стать языком компьютеров пятого поколения.


В свою очередь, информатика как наука начала оформляться вместе с созданием и бурным развитием вычислительной техники. Ее формирование и определение ее предмета продолжаются по настоящее время. Информатика — наука о хранении, обработке и передаче информации с помощью компьютеров. Она включает в себя крупные разделы, изучающие алгоритмические, программные и технические средства хранения, обработки и передачи информации. Математическая логика оказалась единственной математической наукой, методы которой стали мощнейшими инструментами познания во всех разделах информатики. Поэтому сколько-нибудь серьезное изучение информатики немыслимо без освоения основ математической логики.


Вторая часть настоящего раздела посвящена тем вопросам математической логики, которые находят наиболее яркое применение в информатике, а также краткому показу того, как математическая логика работает в некоторых разделах информатики. Здесь будет рассказано о применении математической логики при исследованиях, посвященных базам данных, базам знаний и системам искусственного интеллекта.


Чтобы компьютер работал, он должен быть оснащен программным обеспечением, т.е. комплексом программ, ориентирующих компьютер на решение задач того или иного класса. (Слово "программа" имеет греческое происхождение и означает "объявление", "распоряжение".) Уже само понятие компьютерной программы, предусматривающее четкое алгоритмическое предписание компьютеру о порядке и характере действий, предусматривает проникновение в программу, а также в процесс ее составления (в программирование) теории алгоритмов. Но при более пристальном рассмотрении процесс проникновения логики в программы и программирование оказывается значительно более глубоким и органичным. Не только один ее раздел — теория алгоритмов — работает здесь, но исключительно действенным оказывается само существо математической логики — ее язык, ее аксиоматические теории, выводы и теоремы в них, свойства этих теорий.


В данном параграфе дается краткий обзор основных направлений, по которым математическая логика внедряется в программирование, из которых программирование возникло и без которых существовать не может.


Теория алгоритмов и математическая логика — фундаментальная основа программирования. В 30-е гг. XIX в. английский математик Чарлз Бэббедж высказал впервые идею вычислительной машины. И только сто лет спустя логики разработали четыре математически эквивалентные модели понятия алгоритма (мы достаточно подробно рассмотрели в предыдущей главе три из них). Именно в теории алгоритмов были предугаданы основные концепции, которые легли в основу принципов построения и функционирования вычислительной машины с программным управлением и принципов создания языков программирования. Идею такой вычислительной машины впервые смогли реализовать болгарский ученый С. Атанасов в 1940 г. и немецкий ученый К. Цузе в 1942 г. Четыре главные модели алгоритма породили разные направления в программировании.


Первая модель — это абстрактная вычислительная машина (А. Тьюринг, Р. Пост). Она явилась абстрактным прообразом реальных вычислительных машин. До сих пор все вычислительные машины в некотором смысле базируются на идее Тьюринга: их память физически состоит из битов, каждый из которых содержит либо 0, либо 1. Программное управление унаследовало от этих абстрактных машин и программу, помещенную в "постоянную память" (идея поместить программу ЭВМ в основную память, чтобы иметь возможность изменять ее в ходе вычислений, принадлежит Джону фон Нейману), а структура команды современной ЭВМ в значительной степени напоминает структуру команды машины Тьюринга. В рамках теории машин Тьюринга откристаллизовались важнейшие для компьютерных приложений логики понятия: вычислимая функция, разрешимая задача, неразрешимая (алгоритмически) задача. Собрано большое количество определений абстрактных вычислительных машин и показано, как каждое из них можно свести к другому подходящей кодировкой входов и выходов.


Другая модель — это рекурсивные функции, идея которых восходит к гильбертовскому аксиоматическому подходу. От них унаследовало свои основные конструкции современное структурное программирование.


Третий способ описания алгоритмов — нормальные алгоритмы А. А. Маркова. Они послужили основой языка Рефал и многих других языков обработки символьной информации.


Четвертое направление в теории алгоритмов — так называемое λ-исчисление — базируется на идеях советского логика Р.Шейнфинкеля и американского логика X. Б. Карри. Оказалось, что для определения всех вычислимых функций достаточно операций так называемой λ-абстракции и суперпозиции. Идеи λ-исчисления активно развиваются в языке Лисп, функциональном программировании и во многих других перспективных направлениях современного программирования.


Математическая логика стала бурно развиваться в начале XX в. на почве казалось бы исключительно далекой от приложений проблемы обоснования математики. Но именно эти исследования положили начало строгому определению алгоритмических языков, показали их колоссальные возможности и принципиальные ограничения, развили технику формализации. Поэтому, когда в программировании было осознано, что всякая программа есть формализация, то возникшие здесь математические проблемы упали на почву, тщательно подготовленную математической логикой.




Описание компьютерных программ с помощью математической логики


Первые попытки применить в программировании развитые логические исчисления и методы формализации предпринял американский логик X. Б. Карри. В 1952 г. он сделал доклад "Логика программных композиций", идеи которого опередили свое время по крайней мере на четверть века. Карри рассмотрел задачу программирования как задачу составления более крупных программ из готовых кусков. Были введены две базисные системы конструкций: первая — последовательное исполнение, разветвление и цикл, вторая — последовательное исполнение и условный переход. Он охарактеризовал логические средства, какие можно использовать для композиции программ из подпрограмм в каждом из этих случаев.


Как известно, компьютер является своего рода "идеальным бюрократом": он не воспримет программу, написанную на не до конца формализованном языке, и приступит к работе лишь после того, как все выражено в полном соответствии с детальными инструкциями. Поэтому в 1960-е гг. на первый план вышли задачи точного определения формальных языков достаточно сложной структуры. Математическая логика, подпитываемая идеями программирования, успешно с ними справилась, разработав описание синтаксиса сложных и богатых по выразительным средствам формальных языков.


В середине 1960-х гг. практически одновременно появился ряд пионерских работ в области описания условий, которым удовлетворяет программа. Советский математик В. М. Глушков в 1965 г. ввел понятие алгоритмической алгебры, послужившее прообразом алгоритмических логик. Ф. Энгелер в 1967 г. предложил использовать языки с бесконечно длинными формулами, чтобы выразить бесконечное множество возможностей, возникающих при разных исполнениях программы. Но наибольшую популярность приобрели языки алгоритмических логик. Эти языки были изобретены практически одновременно американскими логиками Р.У.Флойдом (1967), С.А.Р.Хоаром (1969) и учеными польской логической школы, например А. Сальвицким и др. (1970).


Алгоритмическая логика (или динамическая логика, или программная логика, или логика Хоара) — раздел теоретического программирования, в рамках которого исследуются аксиоматические системы, представляющие средства для задания синтаксиса и семантики языков программирования, а также для синтеза компьютерных программ и их верификации (проверки правильности работы). Языки алгоритмических логик основываются на логике предикатов 1-го порядка и включают в себя высказывания вида [math]\{A\}S\{B\}[/math], читающиеся следующим образом: "Если до исполнения оператора [math]S[/math] было выполнено [math]A[/math], то после него будет выполнено [math]B[/math]". Здесь [math]A[/math] называется предусловием, [math]B[/math] — постусловием, либо обещанием [math]S[/math]. На этом языке даются логические описания операторов присвоения и условного перехода, ветвления, цикла.


Наряду с динамической логикой 1-го порядка рассматривается пропозициональная динамическая логика и ее обобщение — так называемая логика процессов, в которой выразимы некоторые свойства программы, зависящие от процесса ее выполнения. Различные динамические логики получаются при варьировании средств языков программирования, используемых в программах. Эти средства содержат массивы и другие структуры данных, рекурсивные процедуры, циклические конструкции, а также средства задания недетерминированных программ.


Динамическая логика является одним из типов логических систем, используемых для логического синтеза компьютерных программ. Логический синтез — один из способов перехода от спецификации программы к реализующему алгоритму, имеющий форму точного рассуждения в некоторой логической системе. В динамической логике спецификация задачи задается в виде двух формул исчисления предикатов — предусловия и постусловия, а аксиомами логической системы являются схемы предусловий и постусловий, связываемых теми или иными конструкциями языка программирования. Синтезируемая программа получается в форме выводимого в динамической логике утверждения, гласящего, что если аргументы задачи удовлетворяют заданному предусловию, то результат выполнения синтезированной программы удовлетворяет заданному постусловию.


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


Принципиально иной способ определения семантики программ, пригодный, скорее для описания всего алгоритмического языка, а не конкретных программ, предложил в 1970 г. американский логик Д. Скотт. Он построил математическую модель λ-исчисления и показал, как переводить функциональное описание языка структурного программирования в λ-исчисление и как определить математическую модель алгоритмического языка через модель λ-исчисления. Эта так называемая денотационная семантика алгоритмических языков, работы по которой насчитываются уже тысячами, стала практическим инструментом построения надежных трансляторов со сложных алгоритмических языков. Так еще одна абстрактная область математической логики нашла прямые практические приложения.




Описание программирования и анализ его концепций с помощью математической логики


Программирование — это процесс составления программы, плана действий. Было замечено, что классическая логика плохо подходит для описания этого процесса хотя бы потому, что она плохо подходит вообще для описания всякого процесса в математике. Еще в начале XX в. стало ясно, что в математике Давно разошлись понятия "существовать" и "быть построенным", с античных времен трактовавшиеся как синонимы. Были выявлены так называемые математические объекты-"привидения" (множества, Функции, числа), существование которых доказано, но построить которые нельзя. Причиной их появления явился эффект сочетания классической логики с теоремой Гёделя о неполноте формальной арифметики. Один из фундаментальных законов классической логики — закон исключенного третьего [math]P\lor\lnot P[/math] в некоторой свободной трактовке фактически означает, что мы знаем все. Этот постулат конечно же никак нельзя назвать реалистическим: мы знаем чрезвычайно мало, и чем больше узнаем, тем лучше это понимаем. Голландский математик Л. Э. Я. Брауэр определил логические корни "привидений" еще до открытия теоремы Гёделя, в 1908 г., и начал построение так называемой интуиционистской математики, не принимающей закон [math]P\lor\lnot P[/math] как универсальный. В 1930–1932 гг. другой голландец А. Гейтинг строго сформулировал логику, которой пользовались в интуиционистской математике, — интуиционистскую логику. Ее математическая интерпретация, данная советским математиком А. Н. Колмогоровым в то же время, сохранила свое значение до сих пор.


А. Н. Колмогоров рассмотрел логику как исчисление задач. Каждая формула алгебры высказываний рассматривается не просто как формула, но как требование решить задачу, т.е. построить объект, удовлетворяющий некоторым условиям. Это так называемая конструктивная интерпретация логики высказываний. Логические связки понимаются как средства построения формулировок более сложных задач из более простых, аксиомы — как задачи, решения которых даны, правила вывода — как способы преобразования решений одних задач в решения других. Отметим, что решение задачи — это не только сам искомый объект, но и доказательство того, что он удовлетворяет предъявляемым требованиям. Например, формула [math]A\land B[/math] понимается в колмогоровской интерпретации как задача, состоящая в том, чтобы построить и решение задачи [math]A[/math], и решение задачи [math]B[/math], правило вывода [math]A,B\setminus A\land B[/math] — как преобразование, строящее из объекта [math]a[/math], решающего задачу [math]A[/math], и объекта [math]b[/math], решающего задачу [math]B[/math], пару [math](a,b)[/math], решающую задачу [math]A\land B[/math]. Объект [math]a[/math], решающий задачу, сопоставленную формуле [math]A[/math], называется реализацией [math]A[/math]. Этот факт обозначается [math]aRA[/math]. Центральным моментом конструктивного понимания логических формул является интерпретация импликации. Конструктивная импликация [math]A\Rightarrow B[/math] понимается как требование построить эффективное преобразование [math]f[/math], применимое ко всем реализациям формулы [math]A[/math] и переводящее их в реализации формулы [math]B[/math].


Нечеткая колмогоровская формулировка логики как исчисления задач породила многочисленные различные конкретизации, дав целую систему точных математических определений. Эта формулировка нашла применение не только в интуиционистской логике, для которой она была создана, но и в других логических системах. Строгие математические семантики, основанные на идее Колмогорова, обычно называют семантиками реализуемости (в отличие от других видов логических семантик, которые базируются на системах истинностных значений). Первую реализуемость построил американский логик С. К. Клини в 1940 г. для арифметики с интуиционистской логикой. В 1960–1980-х гг. появились десятки понятий реализуемости как для систем, базирующихся на интуиционистской логике, так и для других логик. Советский логик А. А. Воронков в 1985 г. вывел условия, при которых классическая логика может рассматриваться как конструктивная. Из них, в частности, следует, что необходимым (но не достаточным) условием конструктивности классической теории является ее полнота, т. е. выводимость в ней для каждой замкнутой формулы [math]F[/math] либо самой [math]F[/math], либо ее отрицания [math]\lnot F[/math]. (Тем самым еще раз подтвердилось прозрение Брауэра относительно логических корней неконструктивности.) Заметим, что примерами классических теорий, имеющих конструктивное истолкование, служат элементарная геометрия и алгебраическая теория вещественных чисел.


Описание программирования с помощью логики основано на определенной аналогии между выводом формулы в некотором логическом исчислении и программой решения задачи, отвечающей этой формуле при конструктивной интерпретации логики. Логическая теория, соответствующая структурным схемам программ, появилась в середине 1980-х гг. Структурные схемы соответствовали нарождающемуся новому типу логики — логики схем программ, которой пользуется программист для создания сложных, многовариантных, итеративных планов действий.


Имеется корректная и полная система конструктивных правил вывода (логика [math]\Omega_1[/math]), позволяющих при помощи некоторой структурной схемы построить доказательство возможности преобразовать [math]A[/math] в [math]B[/math] на базе заданных функций, преобразующих некоторые [math]A_{i}[/math] в [math]B_{i}\colon[/math]


[math]\frac{A \Rightarrow B,\, C \Rightarrow D}{A\lor C \Rightarrow B\lor D}[/math] правило условного оператора (ПУО);


[math]\frac{A\to B,\, B \Rightarrow C,\, C\to D}{A \Rightarrow D}[/math] правило релаксации (ПР);


[math]\frac{A\lor C \Rightarrow B\lor C}{A \Rightarrow B}[/math] правило зацикливания (ПЗ);


[math]\frac{A \Rightarrow A}{\lnot A}[/math] правило бесконечного цикла (ПБЦ);


[math]\frac{\lnot A}{A \Rightarrow A}[/math] правило ошибки (ПО).


Все перечисленные правила имеют конструктивное истолкование. Для ПУО и ПЗ оно видно из их названий. В ПУО [math]A[/math] и [math]B[/math] — охраны создаваемого условного оператора, в ПЗ [math]C[/math] — инвариант создаваемого цикла, [math]B[/math] — условие его завершения. ПР не преобразует, оно лишь говорит о том, что то же/можно использовать и в более частной ситуации, чем [math]A\Rightarrow B[/math]. ПБЦ утверждает, что застой не может быть вечен. ПО постулирует существование нигде не определенной функции "ошибка".


Таким образом, конструктивное описание ситуации, где классические средства работают плохо, оказалось весьма компактным и эффективным.


Логика [math]\Omega_1[/math] — лишь одна из простейших логик схем программ, успешно используемых в автоматизированном планировании действий.


Интуиционистская логика из конструктивных логик с точки зрения синтаксиса оказалась наиболее близкой к традиционной логике. Она полностью сохранила язык исчисления предикатов и логические связки классической логики (вложив в них свой, конструктивный, смысл). Она по возможности сохранила и правила классической логики постольку, поскольку они не влекут закона исключенного третьего [math]P\lor\lnot P[/math] (принципа всезнания в конструктивной интерпретации) или закона двойного отрицания [math]\lnot\lnot P\Rightarrow P[/math]. Этот закон, как выяснил Брауэр, влечет принцип всезнания, а сам конструктивно может быть истолкован как существование Общего Решателя Задач (такой же нонсенс, как вечный двигатель).


Задача построения программы выражается в интуиционистской логике с помощью функциональной формулы:


[math](\forall x) \bigl[A_1\land \ldots\land A_n\, \Rightarrow\, (\exists y)(B_1\land \ldots\land B_k)\bigr].[/math]

Здесь [math]x[/math] — входные переменные создаваемой программы; [math]y[/math] — ее результаты; [math]B_{i}[/math] задают связи вход-выход; [math]A_{i}[/math] амбивалентны: они могут представлять как условия на входы, так и входные значения сложных типов (структуры данных, функции-параметры). Например, формула


[math](\forall x) \bigl[x \geqslant 0\,\Rightarrow\, (\exists y)(y \geqslant 0\land y^2=x)\bigr][/math]

интерпретируется как задача построения программы вычисления квадратного корня из [math]x[/math], а формула


[math]\begin{gathered}(\forall x,y)\Big[ x<y\land (\forall z)\bigl(x \leqslant z\land z\leqslant y \Rightarrow (\exists u)(M(z,u))\bigr)\,\Rightarrow\\ \Rightarrow (\exists v)(\forall z,u)\bigl(x \leqslant z\land z \leqslant y\land M(z,u)\Rightarrow u<v\bigr)\Big] \end{gathered}[/math]

может трактоваться как задача построения оператора, ставящего в соответствие функции, определенной на [math][x,y][/math] и перерабатывающей [math]z[/math] в [math]{u}[/math], ее верхнюю границу.


Из-за отсутствия принципа всезнания выразительные возможности интуиционистской логики существенно повышаются по сравнению с классической. На ее языке можно постулировать не только знание, но порой и незнание. Например, то, что значение действительного числа не может быть задано точно, постулируется в форме


[math]\lnot(\forall x,y)(x>y\lor y>x\lor x=y).[/math]

Но повышение выразительной силы и "аккуратности" выводов в интуиционистской логике по сравнению с классической имеет и оборотную сторону: поиск вывода в ней существенно сложнее. В частности, метод резолюций в ней неприменим, потому что формулы не могут быть разложены на дизъюнкты.


Интуиционистская логика хорошо подходит для описания задач, в которых требуется вычислить новые величины по заданным величинам и ни один из вычислительных ресурсов (время, память, надежность) не налагает ограничений. Для других ситуаций интуиционистскую логику приходится варьировать.


Впервые на возможности логики в качестве инструмента анализа понятий программирования обратили внимание в середине 1970-х гг. в связи с развитием теории верификации (проверки правильности) программ. Оказалось, что для многих конструкций программирования, совместное использование которых не приводит к алгоритмической невычислимости, невозможно построить полную формальную систему, описывающую их взаимодействие. Например, рассмотрение логики схем программ позволило установить, что операторы GOTO естественно получаются в том случае, если все рассматриваемые действия можно считать глобальными преобразованиями состояния системы. Циклы оказались хорошо совместимы с массивами и плохо — с рекурсивными структурами данных, а процедуры высших типов — наоборот. Массивы и сложные структуры данных плохо совместимы с присваиваниями (в данном случае присваивание дается на целый ряд операторов, несущих различный логический смысл).


В общем, можно сделать вывод, что нет средств программирования хороших либо плохих самих по себе; хороши или плохи их комбинации. При этом любая логически разумная комбинация оказывается неуниверсальной, она приспособлена лишь для определенного класса задач.




Верификация (доказательство правильности) программ с помощью математической логики


Широчайшее использование компьютеров в самых разнообразных сферах человеческой деятельности выдвигает на одно из первых мест вопрос о надежности программного обеспечения компьютеров. Как известно, правильность созданной программы обычно проверяется на ряде так называемых тестовых примеров, на начальных данных, для которых результат известен или его можно предсказать. Нетрудно понять, что такая проверка способна лишь выявить наличие ошибок в программе, но не доказать их отсутствие, что, разумеется, не одно и то же.


Поэтому исключительно важна задача строгого доказательства правильности программ, и именно для этой цели и начали разрабатываться программные и динамические логики.


С интуиционистской точки зрения программа будет правильной, если в результате ее выполнения будет достигнут тот результат, с целью получения которого и была написана программа. (Обратите внимание, что мы не рассматриваем программы, содержащие синтаксические ошибки, т. е. предполагаем, что с точки зрения языка программирования программа написана безупречно.) Доказательство правильности программы состоит в предъявлении такой цепочки аргументов, которые убедительно свидетельствуют о том, что это действительно так, т.е. что программа на самом деле решает поставленную задачу.


Сформулируем теперь точное определение понятия правильности программы. Пусть [math]\alpha[/math] — программа, [math]P[/math] — утверждение, относящееся к входным данным, которое должно быть истинно перед выполнением программы [math]\alpha[/math] (оно называется предусловием программы [math]\alpha[/math]), [math]Q[/math] — утверждение, которое должно быть истинно после выполнения программы [math]\alpha[/math] (оно называется постусловием программы [math]\alpha[/math]). Различают два вида правильности программ: частичную и тотальную (полную). Программа [math]\alpha[/math] называется частично правильной относительно предусловия [math]P[/math] и постусловия [math]Q[/math], если всякий раз, когда перед выполнением [math]\alpha[/math] предусловие [math]P[/math] истинно для входных значений переменных и [math]\alpha[/math] завершает работу, постусловие [math]Q[/math] также будет истинно для выходных значений переменных. В этом случае будем использовать запись [math]P[\alpha]Q[/math]. Программа а называется тотально правильной относительно [math]P[/math] и [math]Q[/math], если она частично правильна относительно [math]P[/math] и [math]Q[/math] и [math]\alpha[/math] обязательно завершает свою работу для входных значений переменных, удовлетворяющих условию [math]P[/math]. В этом случае пишем [math]P[\alpha!]Q[/math].


Обратим внимание на то, что понятие правильности программы а сформулировано относительно соответствующих утверждений (условий) [math]P[/math] и [math]Q[/math]. Поэтому из истинности утверждения [math]P[\alpha]Q[/math] (или [math]P[\alpha!]Q[/math] соответственно) не обязательно следует истинность утверждения о правильности программы при других пред- и постусловиях. Аналогичным образом, замена в [math]P[\alpha]Q[/math] (или [math]P[\alpha!]Q[/math]) программы [math]\alpha[/math] на программу [math]\beta[/math], вообще говоря, не сохраняет истинностного значения утверждения о правильности. Не следует также думать, что при данных условиях [math]P[/math] и [math]Q[/math] существует только одна программа [math]\alpha[/math], для которой высказывание [math]P[\alpha]Q[/math] (или [math]P[\alpha!]Q[/math]) истинно.


Говорить о правильности программы самой по себе бессмысленно. Программа пишется с целью решения той или иной конкретной задачи. Каждая правильно поставленная задача содержит в себе условие (то, что дано) и вопрос, на который нужно дать ответ. При составлении программы условие задачи превращается в предусловие, а вопрос преобразуется в постусловие, имеющее уже форму не вопроса, а утверждения, которое должно быть истинно всякий раз, когда ответ на вопрос задачи правилен.


Из определений следует, что всякая тотально правильная программа является частично правильной при тех же пред- и постусловиях. Обратное конечно же неверно. Ясно, что тотальная правильность "лучше" частичной, хотя доказать тотальную правильность программы, по-видимому, сложнее, чем частичную.


Для доказательства частичной правильности операторных программ обычно используются различные модификации метода Флойда, который состоит в следующем. На схеме программы выбираются контрольные точки так, чтобы любой циклический путь проходил по крайней мере через одну точку. С каждой контрольной точкой ассоциируется специальное условие (индуктивное утверждение или инвариант цикла), которое истинно при каждом переходе через эту точку. С входом и выходом схемы также ассоциируются пред- и постусловия. Затем каждому пути программы между двумя соседними контрольными точками сопоставляется так называемое условие правильности. Выполнимость всех условий правильности гарантирует частичную правильность программы.


Один из способов доказательства завершения работы программы состоит во введении в программу дополнительных счетчиков для каждого цикла и в доказательстве их ограниченности в процессе доказательства частичной правильности программы.


Одна из модификаций метода Флойда заключается в построении конечной аксиоматической системы (так называемой "логики Хоара"), состоящей из схем аксиом и правил вывода, в которой в качестве теорем выводимы утверждения о частичной правильности программ, в частности на языке программирования Паскаль. Такая система используется и для задания аксиоматической семантики языка Паскаль. Аксиоматические системы, родственные логике Хоара, разработаны и для других алгоритмических языков программирования.


Для доказательства правильности рекурсивных программ используется метод математической индукции, связанный с определением наименьшей неподвижной точки, а для программ со сложными структурами данных (например, графами, деревьями) — индукция по структуре данных. При этом в теоретических исследованиях по логике Хоара рассматриваются обычные свойства аксиоматизаций в логике — их непротиворечивость и полнота.




Пример 38.2. Рассмотрим простой пример программы и проанализируем ее на правильность. Требуется составить программу, которая для любых натуральных хи у давала бы ответ на вопрос, делится ли [math]x[/math] на [math]y[/math]. Будем писать "[math]y|x[/math]" вместо "[math]x[/math] делится на [math]y[/math]". Тогда определение делимости можно записать следующим образом:


[math](\forall x\in \mathbb{N})(\forall y\in \mathbb{N}) \bigl[y|x \leftrightarrow (\exists n\in \mathbb{N})(ny=x)\bigr].[/math]

Что должен делать компьютер, чтобы выяснить, существует ли такое [math]n[/math], что [math]ny=x[/math]? Заменим последнее условие на следующее [math]x=y+y+\ldots+y[/math] (п слагаемых), которое, в свою очередь, равносильно следующему: [math]0=x-y-y-\ldots-y[/math] ([math]n[/math] вычитаемых). Проверка этого условия уже под силу компьютеру: нужно организовать цикл, который на каждом шаге вычитает сначала из [math]x[/math] число [math]y[/math], затем из результата снова [math]y[/math], и так до тех пор, пока не получится число 0 или отрицательное число. В первом случае ответ на вопрос о делимости [math]x[/math] на [math]y[/math] будет положительным, во втором — отрицательным. Структурная схема программы, реализующей указанный алгоритм, представлена ниже.


Итак, условие, налагаемое на входные данные: [math]x\in\mathbb{N},~ y\in\mathbb{N}[/math]; условие, которое необходимо проверить: [math]z=0[/math]. Докажем правильность этой структурной схемы и соответствующей программы методом от противного. Суть этого метода состоит в следующем. Допустим, что требуется доказать утверждение [math]A[/math]. Мы принимаем гипотезу о том, что [math]A[/math] ложно, т.е. истинно [math]\lnot A[/math]. Если из [math]\lnot A[/math] удается вывести противоречие [math]B\land\lnot B[/math], то заключаем, что [math]\lnot A[/math] на самом деле ложно (поскольку влечет противоречие). Значит, истинно [math]A[/math].


В случае с рассматриваемой программой мы хотим доказать, что выдача нашей программой результата [math]z=0[/math] действительно будет происходить в том и только в том случае, когда [math]x[/math] делится на [math]y[/math]. Это значит, что доказываемое утверждение [math]A[/math] имеет вид: "[math]z=0 \leftrightarrow y|x[/math]". Допустим, что это утверждение неверно. Это означает, что ложна одна из импликаций "[math]z=0\to y|x[/math]" или "[math]y|x\to z=0[/math]". Предположим сначала, что ложна первая импликация "[math]z=0\to y|x[/math]", то есть [math]z=0[/math] истинно, но [math]x[/math] на [math]y[/math] не делится. В соответствии со структурной схемой [math]z=0[/math] либо когда [math]x=0[/math], либо когда в процессе вычитания [math]x-y-y-\ldots-y[/math] на некотором шаге получится 0. В первом случае [math]x[/math] делится на [math]y[/math] в противоречие с допущением. То же происходит и во втором случае. Если [math]x-y-y-\ldots-y=0[/math], то [math]x-ny=0[/math], то есть [math]x=ny[/math] для некоторого [math]n[/math], что и означает делимость [math]x[/math] на [math]y[/math].


Допустим теперь, что ложна вторая импликация, т.е. что [math]x[/math] делится на [math]y[/math], но [math]z\ne0[/math]. Неравенство [math]z\ne0[/math] может иметь место в одном из следующих трех случаев:

а) при невыполнении условия [math]x\geqslant y[/math], то есть при [math]x<y[/math];

б) после завершения цикла;

в) в ходе выполнения цикла.


Поскольку присваивание [math]z\,\colon\!=x[/math] при [math]x\ne0[/math] приводит к вхождению в цикл (так как из [math]x\ne0[/math] следует [math]x>0,\, x>0[/math]), поэтому никаких других возможностей для [math]z\ne0[/math], кроме указанных трех, на структурной схеме не существует. Рассмотрим последовательно эти три возможности:


а) если [math]x<y[/math], то по структурной схеме [math]z\,\colon\!=-1[/math], то есть [math]z\ne0[/math], а значит, [math]x[/math] не делится на [math]y[/math] в противоречие с допущением;


б) если цикл завершен и [math]z\ne0[/math], то [math]z<0[/math]. Это означает, что [math]x-ky<0[/math], то есть [math]x<ky[/math] и [math]\lnot(\exists n\in \mathbb{N})(x=ny)[/math]. Следовательно, [math]x[/math] не делится на [math]y[/math], что также противоречит допущению;


в) [math]z\ne0[/math] в ходе выполнения цикла. Но в этом случае мы и не требуем, чтобы был дан ответ на вопрос о делимости [math]x[/math] на [math]y[/math]. Мы рассчитываем его получить после выполнения программы, а не в ходе ее выполнения. Но только что было доказано, что если цикл завершится с [math]z\ne0[/math], то [math]x[/math] на [math]y[/math] не делится. Здесь мы обнаруживаем, что цикл может и не завершиться, если [math]z[/math] будет оставаться положительным. Это будет происходить в том случае, когда [math]y=0[/math] и [math]z-0=z= x>0[/math]. Но так и должно быть: ведь в этом случае мы будем пытаться установить, делится ли положительное число [math]x[/math] на 0, а такая операция запрещена.


Таким образом, мы устанавливаем, что если наша программа завершится, то она решит поставленную задачу, т.е. мы доказали частичную правильность нашей программы. Чтобы сделать нашу программу тотально правильной, нужно предусмотреть ее завершение и в выявленном исключительном случае. Для этого можно, например, перед проверкой условия [math]x\geqslant y[/math] проверить утверждение "[math]x>0\land y=0[/math]". Если оно истинно, перейти к присваиванию [math]z\,\colon\!-1[/math], если же оно ложно — перейти к проверке [math]x\geqslant y[/math].


В заключение отметим, что исследования по логическому описанию программ и программирования убедительно показали, что логический анализ позволяет анализировать концепции языков программирования и находить многие скрытые недоработки в этих концепциях. Пожалуй, до сих пор это применение строгих математических методов в качестве инструмента критики концепций (зачастую совершенно не продуманных, вставляемых ради "усиления" языков программирования) является важнейшим применением математической логики в области программирования.


Часовой пояс: UTC + 4 часа [ Летнее время ]


Яндекс.Метрика

Copyright © 2010-2016 MathHelpPlanet.com. All rights reserved