我正在阅读 SICP,很难理解为无限流提供的一个例子:
https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-24.html#%_sec_3.5.2
我们可以通过使用诸如add-streams之类的操作来操作流来做更多有趣的事情,从而产生两个给定流的元素和:62
(define (add-streams s1 s2)
(stream-map + s1 s2))
现在我们可以定义整数如下:
(define integers (cons-stream 1 (add-streams ones integers)))
我显然可以理解integers
定义背后的意图,但我正在努力在脑海中"模拟"这个流。之前的例子不是问题,因为状态的维护是明确的。如本例所示:
(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))
这很像递归过程的定义:一对汽车是 1,其 cdr 是评估 1 的承诺。评估 cdr 再次给我们一个 1 和一个评估 cdr 的承诺,依此类推。
也许这句话让我失望了。 一很简单,因为在每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))))
然后定义延迟,使得(延迟(等价于
(memo-proc (lambda () <exp>))
如果我们的流被记住了,那么作为参数传递给add-streams
的integers
总是比我们正在枚举的integers
"落后一步",因此它总是可以访问记忆值。 (parens(中的数字显示了记忆值的使用:
, 1, 1, ... \整数: 1, (2(, (3(, (4(, (5(, (6(, ... === === === === === === 结果 : 1 2, 3, 4, 5, 6, 7, ... 记忆:2、3、4、5、6
,如果我们的流没有被记住,那么每次调用stream-cdr
时integers
都会创建一个新的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 次的元素和integers
的stream-car
生成的,该元素是之前 99 次调用integers
的结果。
虽然第一个add-streams
只组合了两个流,但第二个流将(在返回1
之后(从一个新的add-streams
返回结果,其中第二个流将是另一个add-streams
的结果:
1, add-streams / 1, 1, 1, ...
1, add-streams / 1, 1, ...
1, add-streams / 1, ...
1, add-streams ...
所以add-streams
有点像使用cons
创建列表,正在创建流对,其中第一个是流的流,第二个是另一对流。
不记住这不是integers
的实际实现,因为它的性能是 O(n^2(:
访问元素的时间 CPU 时间元素 整数(毫秒( ========== ======== 1 st 0 2 nd 0 4 日 0 8日 0 16日 0 32 第 47 届 64 th 78 128 th 313 256 th 1,171 512 th 4,500 1024日 17688 2048 th 66,609 4096 号 272531
通过最简单的非记忆流实现,我们得到:
(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
=
....
事实上,我们看到一个三角形的二次计算在这里展开。
见脚注56。
cons-stream
必须是特殊形式。如果cons-stream
是一个程序,那么,根据我们的评估模型,评估(cons-stream <a> <b>)
会自动导致<b>
被评估,这正是我们不希望发生的事情。
我在这里遗漏的是integers
根本没有被重新评估。add-streams
返回的承诺是每个输入流的stream-cdr
。 前面提到的"状态"在反馈循环中得到维护。
它非常令人费解,老实说,它的力量似乎仍然几乎是神奇的。