
4.1. Метациклический интерпретатор
361
(define (f x)
(define (even? n)
(if (= n 0)
true
(odd? (- n 1))))
(define (odd? n)
(if (= n 0)
false
(even? (- n 1))))
hостаток тела fi)
Здесь нам хочется, чтобы имя odd? в теле процедуры even? ссылалось на процедуру
odd?, которая определена позже, чем even?. Область действия имен и odd? — это все
тело f, а не только та его часть, которая лежит за точкой внутри f, где определяется
odd?. В самом деле, ели заметить, что сама odd? определена с помощью even? — так
что even? и odd? являются взаимно рекурсивными процедурами, — то становится ясно,
что единственная удовлетворительная интерпретация двух define — рассматривать их
так, как будто even? и odd? были добавлены в окружение одновременно. В общем
случае, сф ерой действия локального имени является целиком тело процедуры, в котором
вычисляется define.
В нынешнем виде интерпретатор будет вычислять вызовы f правильно, но причи-
на этого «чисто случайная»: поскольку определени я внутренних процедур расположены
в начале, никакие их вызовы не вычисляются, пока они все не определены. Следова-
тельно, к тому времени, когда выполняется even?, odd? уже определена. Фактически,
последовательный механизм вычисления дает те же результаты, что и механизм , непо-
средственно ре ализующий одновременное определение, для всякой процедуры, где внут-
ренние опреде ления стоят в начале тела, а вычисление выражений для определяемых
переменных не использует ни одну из этих переменных. (Пример процедуры, которая не
удовлетворяет этим требованиям, так что последовательное определение не равносильно
одновременному, можно найти в упражнении 4.19.)
24
Однако имеется простой способ обрабатывать определения так, чтобы у локально
определенных имен оказалась действительно общая сфера действия, — достаточно лишь
создать все будущие внутренние переменные текущего окружения, прежде чем начнется
вычисление какого -либо из выражений, возвращающих значение. Можно это сделать,
например, путем синтаксического пр еобразования lambda-выражений. Прежде чем вы-
полнять тело выражения lambda, мы «прочесываем» его и уничтожаем все внутренние
определения. Локально определенные переменные будут созданы через let, а затем по-
лучат значения посредством присваивания. Например, процедура
(lambda hпеременныеi
(define u he1i)
24
Нежелание зависеть в программах от этого механизма вычисления побудило нас написать «администрация
ответственности не несет» в примечании 28 в главе 1. Настаивая на том, чтобы внутренние определения стояли
в начале тела и не использовали друг друга во время вычисления самих определений, стандарт IEEE Scheme
дает авторам реализаций некоторую свободу при выборе механизма вычисления этих определений. Выбор того
или иного правила вычисления может показаться мелочью, которая влияет только на интерпретацию «плохих»
программ. Однако в разделе 5.5.6 мы увидим, что через переход к модели с одновременным определением
внутренних переменных можно избежать некоторых досадных трудностей, которые бы в противном случае
возникли при написании компилятора.