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


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




[97]

((pred (stream-car stream)) (cons-stream (stream-car stream) (stream-filter pred

(stream-cdr stream)))) (else (stream-filter pred (stream-cdr stream)))))

Stream-filter проверяет stream-car потока (то есть car пары, то есть 10000). Поскольку это не простое число, stream-filter смотрит на streamcdr своего входного потока. Вызовstream-cdr приводит к вычислению задержанного вызова stream-enumerate-interval, возвращающего

(cons l000l

(delay (stream-enumerate-interval l0002 l000000)))

Теперь stream-filter смотрит на stream-car этого потока, 10001, видит, что и это не простое число, снова зовет stream-cdr и так далее, пока stream-enumerate-interval не выдаст простое число 10007. Тогда streamfilter, в соответствии со своим определением, вернет

(cons-stream (stream-car stream)

(stream-filter pred (stream-cdr stream)))

что в данном случае равняется

(cons l0007 (delay

(stream-filter prime? (cons l0008 (delay

(stream-enumerate-interval l0009

l000000))))))

Теперь этот результат передается в stream-cdr из нашего исходного выражения. При этом вызывается задержанный stream-filter, который, в свою очередь, вынуждает задержанные вызовы stream-enumerate-interval, пока не доберется до следующего простого числа, а именно 10009. Наконец, результат, передаваемый в stream-car нашего исходного выражения, равен

(cons l0009

(delay

(stream-filter prime?

(cons l00l0

(delay

(stream-enumerate-interval l00ll

l000000))))))

Stream-car возвращает 10009, и вычисление закончено. На простоту было проверено ровно столько чисел, сколько было необходимо, чтобы найти второе простое число на интервале, и сам интервал был перебран только до того места, которое было нужно фильтру простых чисел.

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


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

Реализация delay и force

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

(delay (выражение))

было синтаксическим сахаром для

(lambda () (выражение))

Force просто вызывает (безаргументную) процедуру, порожденную delay, так что она может быть реализована как процедура

(define (force delayed-object) (delayed-object))

При такой реализации delay и force работают согласно описанию, однако к ней можно добавить важную оптимизацию. Во многих приложениях мы вынуждаем один и тот же задержанный объект по многу раз. В рекурсивных программах с использованием потоков это может привести к существенной неэффективности (см. упражнение 3.57). Решение состоит в том, чтобы строить задержанные объекты так, чтобы при первом вынуждении они сохраняли вычисленное значение. Последующие обращения будут просто возвращать сохраненное значение без повторения вычислений. Другими словами, мы реализуем delay как особого рода мемоизированную процедуру, подобную описанным в упражнении 3.27. Один из способов этого добиться - использовать следующую процедуру, которая принимает процедуру (без аргументов) и возвращает ее мемоизированную версию. При первом вызове мемоизированная процедура сохраняет результат. При последующих вызовах она просто его возвращает.

(define (memo-proc proc)

(let ((already-run? false) (result false)) (lambda ()

(if (not already-run?)

(begin (set! result (proc))

(set! already-run? true) result)

result))))

Теперь можно определить delay таким образом, что (delay (выражение))

равносильно

(memo-proc (lambda () (выражение)))


а определение force не меняется.58 Упражнение 3.50.

Закончите следующее определение, которое обобщает процедуру stream-map, чтобы она позволяла использовать процедуры от нескольких аргументов, подобно map из раздела 2.2.1, сноска 12.

(define (stream-map proc . argstreams) (if ((??) (car argstreams)) the-empty-stream

( (??)

(apply proc (map (??) argstreams)) (apply stream-map

(cons proc (map ( ?? ) argstreams))))))

Упражнение 3.51.

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

(define (show x) (display-line x)

x)

Что печатает интерпретатор в ответ на каждое выражение из следующей последователь-ности?59

(define x (stream-map show (stream-enumerate-interval 0 l0))) (stream-ref x 5) (stream-ref x 7)

Упражнение 3.52.

Рассмотрим последовательность выражений (define sum 0)

(define (accum x)

(set! sum (+ x sum))

58Есть много возможных реализаций потоков помимо описанной в этом разделе. Задержанное вычисление, ключевой элемент, который делает потоки практически полезными, было частью метода передачи параметров по имени (by name) в языке Алгол-60. Использование этого механизма для реализации потоков впервые было описано Ландином (Landin 1965). Задержанное вычисление для потоков ввели в Лисп Фридман и Уайз (Friedman and Wise 1976). В их реализации cons всегда задерживает вычисление своих аргументов, так что списки автоматически ведут себя как потоки. Мемоизирующая оптимизация известна также как вызов по необходимости (by need). В сообществе программистов на Алголе задержанные объекты из нашей первой реализации назывались бы санками вызова по имени (call-by-name thunks), а оптимизированный вариант санками вызова по необходимости (call-by-need thunks).

59Упражнения типа 3.51 и 3.52 помогают понять, как работает delay. С другой стороны, смешение задержанного вычисления с печатью - или, хуже того, с присваиванием, - ужасно запутывает, и преподаватели, читающие курсы по языкам программирования, часто пытают студентов экзаменационными вопросами вроде упражнений из этого раздела. Незачем и говорить, что писать программы, зависящие от таких тонкостей, - показатель чрезвычайно плохого стиля. Отчасти мощность потокового программирования в том и заключается, что можно игнорировать порядок, в котором на самом деле происходят события в программах. К сожалению, ровно этого мы и не можем себе позволить в присутствии присваивания, заставляющего нас думать о времени и изменении.



[стр.Начало] [стр.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]