1.2. Процедуры и порождаемые и ми процессы
51
В общем случае, итеративный процесс — это такой процесс, состояние которого
можно описать конечным числом переменных состояния (state variables) плюс заранее
заданное правило, определяющее, как эти переменные состояния изменяются от шага к
шагу, и плюс (возможно) тест на завершение, который определяет условия, при которых
процесс должен закончить работу. При вычислении n! число шагов ли нейно растет с
ростом n. Такой процесс называется линейно итеративным процессом (linear iterative
process).
Можно посмотреть на различие этих двух процессов и с другой точки зрения. В
итеративном случае в каждый момен т переменные программы дают полное описание
состоя ния процесса. Если мы остановим процесс между шагами, для продолжения вы-
числений нам будет достаточно дать интерпретатору значения трех переменных про-
граммы. С рекурсивным процессом это не так. В этом случае имеется дополнительная
«спрятанная» информация, которую хранит интерпретатор и ко торая не содержится в
переменных программы. Она указывает, «где находится» процесс в терминах цепочки
отложенных операций . Чем длиннее цепочка, тем больше информации нужно хранить
30
.
Противопоставляя итерацию и рекурсию, нужно вести себя осторожно и не сме-
шивать понятие рекурсивного процесса с понятием рекурсивной процедуры. Когда мы
говорим, что процедура рекурсивна, мы имеем в виду факт синтаксиса: определение про-
цедуры ссылается (прямо или косвенно) на саму эту процедуру. Когда же мы говорим о
процессе, что он следует, скажем, линейно рекурсивной схеме, мы говорим о развитии
процесса, а не о синтаксисе, с помощью которого написана процедура. Может показаться
странным, например, высказывание «рекурсивная процедура fact-iter описывает ите-
ративный процесс». Однако процесс действительно является итеративным: его состояние
полно стью описывается тремя переменными состояния, и чтобы выполнить этот процесс,
интерпретатор должен храни ть значение только трех переменных.
Различие между процессами и процедурами может запутывать о тчасти потому, что
большинс тво реализаций обычных языков (включая Аду, Паскаль и Си) построены так,
что интерпретация любой рекурсивной процедуры поглощает объем памяти, линейно рас-
тущий пропорционально количеству вызовов процедуры, даже если описываемый ею про-
цесс в принципе итеративен. Как следствие, эти языки способны описывать итеративные
процессы только с помощью специальных«циклических конструкций» вроде do, repeat,
until, for и while. Реализация Scheme, которую мы рассмотрим в главе 5, свободна
от этого недостатка. Она будет выполнять итеративный процесс, используя фиксирован-
ный о бъем памяти, даже если он описывается рекурсивной процедурой. Такое свойство
реализации языка называется поддержкой хвосто вой рекурсии (tail recursion)
∗
. Если
реализация языка поддерживает хвостовую рекурсию, то итерацию можно выразить с
помощью обыкновенного механизма вызова функций, так что специальные циклические
конструкции имеют смысл только как синтаксический сахар
31
.
30
Когда в главе 5 мы будем обсуждать реализацию процедур с помощью регистровых машин, мы увидим, что
итеративный процесс мож но реализовать «в аппаратуре» как маши ну, у которой есть только конечный набор
регистров и нет никакой дополнительной памяти. Напротив, для реализации рекурсивного процесса требуется
машина со вспомогательной структурой данных, называемойстек (stack).
∗
Словарь multitran.ru дает перевод «концевая рекурсия». Наш вариант, как кажется, изящнее и сохра-
няет метафору, содержащуюся в англоязычном термине. — прим. перев.
31
Довольно долго считалось, что хвостовая рекурсия — особый трюк в оптимизирующих компиляторах.
Ясное семантическое основание хвостовой рекурсии было найдено Карлом Хьюиттом (Hewitt 1977), который
выразил ее в терминах модели вычислений с помощью «передачи сообщений» (мы рассмотрим эту модель в