Ремонт принтеров, сканнеров, факсов и остальной офисной техники


назад Оглавление вперед




[119]

и приведите пример ситуации, когда имеет смысл, чтобы 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) делается попытка прояснить многомерную путаницу, которая здесь возникает.


Преобразование интерпретатора

Основная разница между ленивым интерпретатором и интерпретатором из раздела 4.1.1 состоит в обработке вызовов процедур внутри eval и apply. Ветка application? в eval принимает вид

((application? exp) (apply (actual-value (operator exp) env) (operands exp) env))

Это почти тот же код, что был в ветке application? в eval из раздела 4.1.1. Однако при ленивом вычислении мы зовем apply с выражениями операндов, а не с аргументами, которые получаются при их вычислении. Мы также передаем apply окружение, поскольку оно понадобится для построения санков, если нам хочется, чтобы аргуметы вычислялись с задержкой. Оператор мы по-прежнему вычисляем, потому что сама применяемая процедура нужна apply, чтобы выбрать действие на основании ее типа (элементарная или составная) и применить ее.

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

(define (actual-value exp env) (force-it (eval exp env)))

чтобы, если значение выражения является санком, оно было вынуждено.

Наша новая версия apply также почти совпадает с версией из раздела 4.1.1. Разница состоит в том, что eval передает ей невычисленные выражения: для элементарных процедур (они строгие) мы вычисляем все аргументы и затем вызываем примитив; для составных процедур (они нестрогие) мы прежде применения процедуры замораживаем все аргументы.

(define (apply procedure arguments env) (cond ((primitive-procedure? procedure) (apply-primitive-procedure procedure

(list-of-arg-values arguments env))) ; изменение ((compound-procedure? procedure) (eval-sequence (procedure-body procedure) (extend-environment (procedure-parameters procedure)

(list-of-delayed-args arguments env) ; изменение (procedure-environment procedure)))) (else (error

"Неизвестный тип процедуры -- APPLY" procedure))))

Процедуры, обрабатывающие аргументы, почти такие же, как list-of-values из раздела 4.1.1, но только list-of-delayed-args замораживает аргументы, вместо того, чтобы их вычислять, а в list-of-arg-values вместо eval используется actual-value:

(define (list-of-arg-values exps env) (if (no-operands? exps)


(cons (actual-value (first-operand exps) env) (list-of-arg-values (rest-operands exps)

env))))

(define (list-of-delayed-args exps env) (if (no-operands? exps) ()

(cons (delay-it (first-operand exps) env)

(list-of-delayed-args (rest-operands exps)

env))))

Кроме того, нам требуется изменить в интерпретаторе обработку if, где вместо eval мы должны вызывать actual-value, чтобы значение предикатного выражения вычислялось прежде, чем мы проверим, истинно оно или ложно:

(define (eval-if exp env)

(if (true? (actual-value (if-predicate exp) env)) (eval (if-consequent exp) env) (eval (if-alternative exp) env)))

Наконец, нужно изменить процедуру driver-loop (раздел 4.1.4), чтобы она звала actual-value вместо eval. Таким образом, если задержанное значение добирается до цикла чтение-вычисление-печать, то оно, прежде чем печататься, будет разморожено. Кроме того, чтобы показать, что работа идет с ленивым интерпретатором, мы изменим подсказки:

(define input-promptВвод L-Eval:")

(define output-promptЗначение L-Eval:")

(define (driver-loop)

(prompt-for-input input-prompt) (let ((input (read))) (let ((output

(actual-value input the-global-environment))) (announce-output output-prompt) (user-print output))) (driver-loop))

Внеся эти изменения, мы можем запустить интерпретатор и протестировать его. Успешное вычисление выражения try, описанного в разделе 4.2.1, показывает, что интерпретатор проводит ленивое вычисление:

(define the-global-environment (setup-environment))

(driver-loop)

;;; Ввод L-eval: (define (try a b)

(if (= a 0) 1 b))

;;; Значение L-Eval: ok



[стр.Начало] [стр.1] [стр.2] [стр.3] [стр.4] [стр.5] [стр.6] [стр.7] [стр.8] [стр.9] [стр.10] [стр.11] [стр.12] [стр.13] [стр.14] [стр.15] [стр.16] [стр.17] [стр.18] [стр.19] [стр.20] [стр.21] [стр.22] [стр.23] [стр.24] [стр.25] [стр.26] [стр.27] [стр.28] [стр.29] [стр.30] [стр.31] [стр.32] [стр.33] [стр.34] [стр.35] [стр.36] [стр.37] [стр.38] [стр.39] [стр.40] [стр.41] [стр.42] [стр.43] [стр.44] [стр.45] [стр.46] [стр.47] [стр.48] [стр.49] [стр.50] [стр.51] [стр.52] [стр.53] [стр.54] [стр.55] [стр.56] [стр.57] [стр.58] [стр.59] [стр.60] [стр.61] [стр.62] [стр.63] [стр.64] [стр.65] [стр.66] [стр.67] [стр.68] [стр.69] [стр.70] [стр.71] [стр.72] [стр.73] [стр.74] [стр.75] [стр.76] [стр.77] [стр.78] [стр.79] [стр.80] [стр.81] [стр.82] [стр.83] [стр.84] [стр.85] [стр.86] [стр.87] [стр.88] [стр.89] [стр.90] [стр.91] [стр.92] [стр.93] [стр.94] [стр.95] [стр.96] [стр.97] [стр.98] [стр.99] [стр.100] [стр.101] [стр.102] [стр.103] [стр.104] [стр.105] [стр.106] [стр.107] [стр.108] [стр.109] [стр.110] [стр.111] [стр.112] [стр.113] [стр.114] [стр.115] [стр.116] [стр.117] [стр.118] [стр.119] [стр.120] [стр.121] [стр.122] [стр.123] [стр.124] [стр.125] [стр.126] [стр.127] [стр.128] [стр.129] [стр.130] [стр.131] [стр.132] [стр.133] [стр.134] [стр.135] [стр.136] [стр.137] [стр.138] [стр.139] [стр.140] [стр.141] [стр.142] [стр.143] [стр.144] [стр.145] [стр.146] [стр.147] [стр.148] [стр.149] [стр.150] [стр.151] [стр.152] [стр.153] [стр.154] [стр.155] [стр.156] [стр.157] [стр.158] [стр.159] [стр.160] [стр.161] [стр.162] [стр.163] [стр.164] [стр.165] [стр.166] [стр.167] [стр.168] [стр.169] [стр.170] [стр.171] [стр.172] [стр.173] [стр.174] [стр.175] [стр.176] [стр.177] [стр.178] [стр.179] [стр.180] [стр.181] [стр.182] [стр.183] [стр.184] [стр.185] [стр.186] [стр.187] [стр.188] [стр.189] [стр.190] [стр.191] [стр.192] [стр.193] [стр.194] [стр.195] [стр.196]