SICP Упражнение 3.52: Нужен ли memo-proc при использовании Scheme (Guile)?

Я делаю SICP ex 3.52. Я получаю правильные ответы на выражения «stream-ref» и «display-stream», а также получаю правильное значение «sum» после каждого выражения, данного в упражнении. Однако все это работает без использования оптимизации «memo-proc», представленной на стр. 439 непосредственно перед перечисленным упражнением.

Я использую Guile в Linux, и единственным дополнением, которое я сделал в коде, было определение синтаксиса в верхней части кода для «cons-stream», чтобы включить форму «задержки».

Возможно, я что-то неправильно понимаю, но я даже не вижу момента, когда при выполнении вызывается «memo-proc». Есть ли что-то, что встроено в Guile, что уже выполняет эту оптимизацию, предоставляемую «memo-proc», которая, согласно SICP, служит для реализации «задержки» как специальной мемоизированной процедуры.

Вот запомненная процедура в SICP...

(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))))

Связанный код для удобства...

(define (stream-ref s n)
  (if (= n 0)
      (stream-car s)
      (stream-ref (stream-cdr s) (- n 1))))

(define (stream-map proc . argstreams)
  (if (stream-null? (car argstreams))
      the-empty-stream
      (cons-stream
        (apply proc (map stream-car argstreams))
        (apply stream-map 
               (cons proc (map stream-cdr argstreams))))))

(define (stream-for-each proc s)
  (if (stream-null? s)
      'done
      (begin (proc (stream-car s))
             (stream-for-each proc (stream-cdr s)))))

(define (display-stream s)
  (stream-for-each display-line s))

(define (display-line x) (newline) (display x))

(define (stream-car stream) (car stream))
(define (stream-cdr stream) (force (cdr stream)))

(define (stream-enumerate-interval low high)
  (if (> low high)
      the-empty-stream
      (cons-stream
        low
        (stream-enumerate-interval (+ low 1) high))))

(define (stream-filter pred stream)
  (cond ((stream-null? stream) the-empty-stream)
        ((pred (stream-car stream))
         (cons-stream (stream-car stream)
                      (stream-filter
                       pred
                       (stream-cdr stream))))
        (else (stream-filter pred (stream-cdr stream)))))

И последовательность выражений для упражнения...

(define sum 0)

(define (accum x) (set! sum (+ x sum)) sum)

(define seq
  (stream-map accum
              (stream-enumerate-interval 1 20)))

(define y (stream-filter even? seq))

(define z
  (stream-filter (lambda (x) (= (remainder x 5) 0))
                 seq))

(stream-ref y 7)

(display-stream z)

Я ожидаю результатов, перечисленных ниже, и я их получаю.

(define sum 0) 
 ;; sum => 0 

 (define (accum x) 
   (set! sum (+ x sum)) 
   sum) 
 ;; sum => 0 

 (define seq (stream-map accum (stream-enumerate-interval 1 20))) 
 ;; sum => 1 

 (define y (stream-filter even? seq)) 
 ;; sum => 6 

 (define z (stream-filter (lambda (x) (= (remainder x 5) 0)) 
                          seq)) 
 ;; sum => 10 

 (stream-ref y 7) 
 ;; sum => 136 
 ;; => 136 

 (display-stream z) 
 ;; sum => 210 
 ;; => (10 15 45 55 105 120 190 210) 

Однако я получаю эти результаты без использования «memo-proc». Однако без оптимизации «memo-proc» я бы ожидал получить «сумму» 15 при:

(define z (stream-filter (lambda (x) (= (remainder x 5) 0)) 
                          seq)) 
;; sum => 10 

потому что функция «accum» будет вызываться несколько раз, так как часть потока «seq» нужно будет перечислить во второй раз. Я надеюсь, что кто-то может помочь мне увидеть, что мне не хватает.


person Kris    schedule 03.05.2019    source источник


Ответы (1)


Я делаю упражнения с Racket (с пакетом SICP), и реализация потоков по умолчанию запоминается (см.: Запоминают ли потоки Racket свои элементы?). Я полагаю, Гайл делает то же самое.

Возможно, поэтому в вопросе говорится «рассмотреть», поскольку нет простого способа проверить поведение без запоминания.

В главе 4 мы сами реализуем язык, например, в 4.18 вы можете реализовать потоки с нуля. Используя эти наивные потоки, я получаю следующий результат для этого упражнения:

Naive Streams
=============
    sum after stream-ref: 162
    sum after display-stream: 362

И после добавления в этот раздел реализации memo-proc:

Naive Streams with Memo-Proc
============================
    sum after stream-ref: 136
    sum after display-stream: 210


Обновление: значение 'sum' в разное время выполнения зависит не только от мемоизации. На это также влияет то, используете ли вы «нечетные» потоки в стиле SICP или более традиционные «четные» потоки, и, если используете современную схему, используете ли вы встроенную карту и фильтр или те, что из текста.

+----------------+-------------+-------------+--------------+--------------+
|                | SICP Scheme | SICP Scheme | Racket With  | Racket With  |
|                |    With     |   Without   |    Text's    |   Built in   |
| sum after:     | Memoization | Memoization | Map & Filter | Map & Filter |
+----------------+-------------+-------------+--------------+--------------+
| define accum   |        0    |       0     |        0     |        0     |
+----------------+-------------+-------------+--------------+--------------+
| define seq     |        1    |       1     |        0     |        0     |
+----------------+-------------+-------------+--------------+--------------+
| define y       |        6    |       6     |        6     |        0     |
+----------------+-------------+-------------+--------------+--------------+
| define z       |       10    |      15     |       10     |        0     |
+----------------+-------------+-------------+--------------+--------------+
| stream-ref     |      136    |     162     |      136     |      136     |
+----------------+-------------+-------------+--------------+--------------+
| display-stream |      210    |     362     |      210     |      210     |
+----------------+-------------+-------------+--------------+--------------+

Для получения дополнительной информации о «нечетных» и «четных» потоках см. «Обоснование» Scheme's SRFI 41 .

Я добавил приведенный выше код на GitHub. .

person codybartfast    schedule 03.05.2019
comment
легко исправить stream->list, явно проверив случай n==1 и вернув затем (list (stream-car x)) без какого-либо ненужного принуждения. на самом деле нет проблем с потоками SICP, за исключением случайной необходимости явной задержки здесь и там IIRC. полностью задержанные потоки требуют больших вычислительных ресурсов. иногда они тоже нужны. - person Will Ness; 12.05.2019