SICP第3.5.2章无限流整数定义

SICP Chapter 3.5.2 infinite streams integers definition

我正在阅读 SICP,但很难理解为无限流提供的一个示例:

https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-24.html#%_sec_3.5.2

We can do more interesting things by manipulating streams with operations such as add-streams, which produces the elementwise sum of two given streams:62

(define (add-streams s1 s2)
  (stream-map + s1 s2))

Now we can define the integers as follows:

(define integers (cons-stream 1 (add-streams ones integers)))

我显然可以理解 integers 定义背后的意图,但我正在努力 "simulate" 我脑海中的这个流。之前的例子不是问题,因为状态的维护是明确的。比如在这个例子中:

(define (integers-starting-from n)
  (cons-stream n (integers-starting-from (+ n 1))))

(define integers (integers-starting-from 1))

我理解这个整数的定义没问题。

书中描述了ones的定义:

(define ones (cons-stream 1 ones))

This works much like the definition of a recursive procedure: ones is a pair whose car is 1 and whose cdr is a promise to evaluate ones. Evaluating the cdr gives us again a 1 and a promise to evaluate ones, and so on.

也许这条线让我失望了。 Ones 很简单,因为在每个 stream-cdr 上都会评估过程并提供新的“1”和下一个承诺。

当我尝试将此推理应用于 integers 时,我很难理解为什么结果流不是“1 2 2 2 2 2 ...”,因为整数会不断重新计算基本上从 1 点重新开始。

编辑 我没有详细说明在我的问题中是否假设了记忆是我的疏忽。 SICP 确实提到了答案中提出的二次行为问题,并以记忆 delay 函数的形式提供了解决方案:

(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 is then defined so that (delay ) is equivalent to

(memo-proc (lambda () <exp>))

参见foot note 56

cons-stream must be a special form. If cons-stream were a procedure, then, according to our model of evaluation, evaluating (cons-stream <a> <b>) would automatically cause <b> to be evaluated, which is precisely what we do not want to happen.

我在这里遗漏的一点是 integers 根本没有被重新评估。 add-streams returns 的承诺是每个输入流的 stream-cdr。 前面提到的"state"在反馈回路中保持。
它非常令人费解,老实说,它的力量似乎仍然很神奇。

如果我们的流被记忆化,那么作为参数传递给 add-streamsintegers 总是 "one step behind" 我们正在枚举的 integers,所以它总是可以访问到记忆值。 (parens) 中的数字显示记忆值的使用:

Integers: 1, add-streams /    Ones: 1,     1,     1,     1,     1,     1,  ...
                         \Integers: 1,    (2),   (3),   (4),   (5),   (6), ...
                                   ===    ===    ===    ===    ===    ===
Results:  1                         2,     3,     4,     5,     6,     7,  ...

Memoized:                           2,     3,     4,     5,     6, 

如果我们的流没有被记忆,那么每次在 integers 上调用 stream-cdr 时,都会创建一个新的 ones 系列,并将其添加到之前的所有 ones 中。

integers                          1
    ones                              1,  1,  1,  1,  1,  1,  ...
    integers                          1
        ones                              1,  1,  1,  1,  1,  ...
        integers                          1
            ones                              1,  1,  1,  1,  ...
            integers                          1
                ones                              1,  1,  1,  ...
                integers                          1
                    ones                              1,  1,  ...
                    integers                          1
                        ones                              1,  ...
                        integers                          1
                                 ==  ==  ==  ==  ==  ==  ==  
                                  1,  2,  3,  4,  5,  6,  7,  ...

所以 100 是通过将 ones 的元素相加 99 次和 integersstream-car 生成的,后者是前 99 次调用 integers.

尽管第一个 add-streams 仅组合两个流,但第二个流将(在返回 1 之后)返回新 add-streams 的结果,其中第二个流将be 将是另一个 add-streams:

的结果
1, add-streams / 1,               1,               1, ...
               \ 1, add-streams / 1,               1, ...
                                \ 1, add-streams / 1, ...
                                                 \ 1, add-streams ...

所以 add-streams,有点像使用 cons 创建列表,正在创建成对的流,其中第一个是 1 的流,第二个是另一对流。

如果不记忆,这不是 integers 的实际实现,因为它的性能是 O(n^2):

Time to Access Elements

Element of    CPU Time
 integers      (msec)
==========    ========
      1 st           0
      2 nd           0
      4 th           0
      8 th           0
     16 th           0
     32 nd          47
     64 th          78
    128 th         313
    256 th       1,171
    512 th       4,500
  1,024 th      17,688
  2,048 th      66,609
  4,096 th     272,531

使用最简单的非记忆化流实现,我们得到:

(define (stream-map2 f s1 s2)
  (cons (f (car s1) (car s2)) 
    (lambda ()
      (stream-map2 f ((cdr s1)) ((cdr s2))))))

(define ones (cons 1 (lambda () ones)))

(define integers
    (cons 1 
      (lambda ()
        (stream-map2 + ones integers)))       ;; 1
  = 
    (cons 1 
      (lambda () 
        (cons (+ (car ones) (car integers))
          (lambda () 
            (stream-map2 + ones 
              (stream-map2 + ones integers))))))      ;; 2
  =
    (cons 1 
      (lambda () 
        (cons (+ (car ones) (car integers))
          (lambda () 
            (let ((i2 (stream-map2 + ones integers)))
              (stream-map2 + ones i2))))))

  = 
    (cons 1 
      (lambda () 
        (cons (+ (car ones) (car integers))
          (lambda () 
            (let ((i2 (cons (+ (car ones) (car integers))   ;; <---- 1
                        (lambda () 
                          (stream-map2 + ones 
                            (stream-map2 + ones integers))))))
              (cons (+ (car ones) (car i2))
                (lambda ()
                  (stream-map2 + ones ((cdr i2))))))))))
  = 
    (cons 1 
      (lambda () 
        (cons (+ (car ones) (car integers))
          (lambda () 
            (cons (+ (car ones) 
                     (+ (car ones) (car integers)))
              (lambda ()
                (stream-map2 + ones 
                  (stream-map2 + ones 
                    (stream-map2 + ones integers)))))))))     ;; 3
  =
    ....

我们确实看到了一个三角形,二次计算在这里展开。