Универсальность скелетного языка

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

incr X:

выполняет вычисление той же функции (функции следования), которая вычисляется машиной Тьюринга в примере из раздела 11.2. Действительно, она увеличивает на единицу значение, присвоенное переменной X. Аналогично, если считать переменные X и Y входами, а переменную 1 — выходом программы

copy Y to Z; while X not 0 do;

incr Z;

deer X; end;

то можно сказать, что она выполняет функцию сложения.

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

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

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

Невычислимая функция

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

Проблема останова

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

while X not 0 do:

incr X; end;

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

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

Предыдущий пример показал, что результат, то есть остановится ли в конечном итоге программа, может зависеть от начальных значений переменных. Поэтому, если мы хотим предсказать, прекратится ли выполнение программы, необходимо соблюдать аккуратность в отношении ее начальных значений. Выбор, который мы собираемся сделать в отношении этих значений, может показаться вам на первый взгляд странным, но не теряйте веры. Наша цель — воспользоваться преимуществами метода под названием самовызов, идея которого заключается в том, что объект обращается к самому себе. Эта уловка часто приводит к удивительным результатам в математике, начиная от таких курьезов, как утверждение «Это утверждение является ложным» до более серьезного парадокса, представленного вопросом «Содержит ли набор всех наборов само себя?». Мы собираемся подготовить ситуацию для последовательности умозаключений, схожей с «Если это есть, то этого нет; но если этого нет, то оно есть».

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

Рассмотрим, что произойдет, если выполнить самовызов для простой программы

while X not 0 do:

incr X: end;

Мы хотим узнать, что произойдет, если запустить эту программу, присвоив переменной X закодированный вариант самой программы (рис. 11.3). В этом случае ответ очевиден. Так как значение X не равно нулю, программа попадет в цикл и никогда не остановится. С другой стороны, если выполнить подобный эксперимент над программой

clear X;

while X not 0 do:

incr X; end:

то она остановится, так как независимо от начального значения на момент достижения структуры while-end значением переменной X будет ноль.

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

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

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

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: