
372
Глава 4. Метаязыковая абстракция
порядков. Проясните детали в обеих позициях. Покажите, как реализовать unless в виде про-
изводного выражения (вроде cond или let), и приведите пример ситуации, когда имеет смысл,
чтобы unless была процедурой, а не особой формой.
4.2.2. Интерпретатор с ленивым вычислением
В этом разделе мы реализуем язык с нормальным порядком вычислений, ко торый
отличается от Scheme только тем, что все составные процедуры по всем аргументам
нестроги. Элементарные процедуры по-прежнему будут строгими. Совсем несложно, мо-
дифицируя интерпретатор из раздела 4.1.1, добиться, чтобы интерпретируемый язык вел
себя таким образом. Почти что все требуемые изменения сосредоточены вокруг механиз-
ма процедурного вызова.
Основная идея состоит в том, что при вызове процедуры интерпретатор должен опре-
делить, какие аргументы требуется вычисли ть, а какие задержать. Задержанные аргу-
менты не вычисляются, а преобразуются в объекты, называемые санками (thunks)
34∗
.
В санке должна содержаться информация, необходимая, чтобы вычислить значение ар-
гумента, когда оно потребуется, и сделать это так, как будто оно вычислено во время
вызова. Таким образом, санк должен содержать выражение-аргумент и окружение, в
котором вычисляется вызов процедуры.
Процесс вычисления санка называется вынуждением (forcing a thunk)
35
Вообще го-
воря, санк вынуждается только тогда, когда требуется его значение: когда он передается
в элементарную процедуру, использующую его значение; когда он служит предикатом в
условном выражении; или когда он является значением оператора, который нужно при-
менить как процедуру. Мы должны решить, будем ли мы мемоизировать (memoize)
санки, как мы делали с задержанными объектами в разделе 3.5.1. При использовании
мемоизации, когда санк вынуждается в первый раз, он запоминает вычисленное значе -
ние. Последующие вызовы только возвращают запомненное значение, не вычисляя его
заново. Мы делаем выбор в пользу мемоизации, поскольку для многих при ложений это
эффективнее. Здесь, однако, имеются тонкости
36
.
34
Название «санк» было придумано в неформальной группе, которая обсуждала реализацию вызова по имени
в Алголе 60. Было замечено, что большую часть анализа («обдумывания», thinking about) выражения можно
производить во время компиляции; таким образом, во время выполнения выражение будет уже большей частью
«обдумано» (thunk about — намеренно неверно образованная английская форма) (Ingerman et al. 1960).
∗
В русскоязычной литературе слово thunk иногда переводится как «переходник». Нам кажется, что в данном
случае такой перевод мешал бы пониманию текста. — прим. перев.
35
Это аналогично использованию слова force («вынудить», «заставить») для задержанных объектов, при
помощи которых в главе 3 представлялись потоки. Основная разница между тем, что мы делаем здесь, и
тем, чем мы занимались в главе 3, состоит в том, что теперь мы встраиваем задержку и вынуждение в
интерпретатор, и они применяются автоматически и единообразно во всем языке.
36
Ленивые вычисления, совмещенные с мемоизацией, иногда называют методом передачи аргументов с
вызовом по необходимости ( call by need), в отличие от вызова по имени (call by name). (Вызов по имени,
введенный в Алголе 60, аналогичен немемоизированному ленивому вычислению.) Как проектировщики языка
мы можем сделать ин терпретатор мемоизирующим или немемоизирующим, или же оставить это на усмотрение
программистов (упражнение 4.31). Как можно было ожидать из главы 3, этот выбор вызывает к ж изни вопросы,
особенно тонкие и запутанные в присутствии присваивания. (См. упражнения 4.27 и 4.29.) В замечательной
статье Клингера (Clinger 1982) делается попытка прояснить многомерную путаницу, которая здесь возникает.