1. 基于一种称为 的数据结构,探索对状态进行模拟的另一条途径,流可能缓和状 态模拟中的复杂性;
  2. 如果用离散的步长去度量时间,那么我们就可以用一个(可能无穷的)序列去模拟一 个时间函数,在这一节里,我们将看到如何用这样的序列去模拟变化,以这种序列表 示被模拟系统随着时间变化的历史;
  3. 引进一种称为 的新数据结构,同时引进一种 延时求值 的技术,使我们能够 用流去表示非常长的(甚至是无穷)序列;
  4. 流处理使我们可以模拟一些包含状态的系统,但却不需要利用赋值或者变动数据(有 利有弊);

流作为延时的表

  1. 流是一种非常巧妙的想法,使我们可能利用各种序列操作,但又不会带来将序列作 为表去操作而引起的代价;
  2. 利用流结构,我们能得到这两个世界里最好的东西:如此形成的程序可以像 序列操 作 那样优雅,同时又能得到 递增计算 的效率;
  3. 本节将要写出的各个程序都像是在处理完整的序列,但我们将要设计出流的一种使 现,使得流的构造和它的使用能够 交错进行, 而这种交错又是完全透明的;
  4. 作为一种数据抽象(盒子指针模形),流与表完全一样,它们的不同点就在于元素 的求值时间,对于常规的表,其 car 和 cdr 部分都是在构造时求值,而对于流, 其 cdr 部分则是在选取的时候才去求值;
  5. 一般而言,可以将延时求值看作一种“由需要驱动”的程序设计,其中流处理的每个 阶段都仅仅活动到足够满足下一阶段需要的程度;

练习 3.50-3.52

3.50

1
2
3
4
5
6
7
(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))))))

3.51

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
;; 基于记忆性过程和延时求值
;; 第一个表达式
;; 0
;; value: x

;; 第二个
;; 1
;; 2
;; 3
;; 4
;; 5
;; value: 5

;; 第三个
;; 6
;; 7
;; value: 7

3.52

 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
(define sum 0)
;; sum 0

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

;; accum

(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)
;; 6
;; 10
;; 28
;; 36
;; 66
;; 78
;; 120
;; 136

;; 136
;; sum 136

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

;; sum 210

;; 如果不使用记忆性过程的话,对 seq 流的求值将会产生重复计算;
;; 而每次重复对 seq 的流进行求值,都会调用 accum;
;; 每一次重复计算都会更新 sum 的值;

无穷流

  1. 我们可以像对待完整的实体一样去对流进行各种操作,即使在实际上只计算出了有 关的流中必须访问的那一部分,利用这种技术我们可以用流去表示无穷长的序列;

练习 3.53-3.62

3.53

1
2
3
4
5
6
;; 1
;; 2
;; 4
;; 8
;; ...
;; (expt 2 n)

3.54

1
2
3
(define (mul-streams s1 s2)
  (stream-map * s1 s2))
(define factorials (cons-stream 1 (mul-streams factorials (stream-cdr integers))))

3.55

1
2
3
4
5
6
(define (partial-sums s)
  (cons-stream
   (stream-car s)
   (add-streams
    (partial-sums s)
    (stream-cdr s))))

3.56

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
(define (merge s1 s2)
  (cond ((stream-null? s1) s2)
	((stream-null? s2) s1)
	(else
	 (let ((s1car (stream-car s1))
	       (s2car (stream-car s2)))
	   (cond ((< s1car s2car)
		  (cons-stream s1car (merge (stream-cdr s1) s2)))
		 ((> s1car s2car)
		  (cons-stream s2car (merge s1 (stream-cdr s2))))
		 (else
		  (cons-stream s1car
			       (merge (stream-cdr s1))
			       (merge (stream-cdr s2)))))))))

(define s (cons-stream 1 (merge
			  (scale-stream s 2)
			  (merge
			   (scale-stream s 3)
			   (scale-stream s 5)))))

3.57

1
2
3
4
;; 使用基于 add-streams 过程的 fibs 流,所需加法步骤是与 n 相关的线性函数;
;; 如果在 delay 的实现中不使用记忆性过程,那每次去取 n-1 与 n-2 的 fib 值,
;; 都会引起这两个值的重复计算;
;; 就像中文版 SICP P24 中最初的 fib 程序,整个过程步数的增长阶是指数级的。

3.58

1
2
3
4
5
6
7
8
9
;; 计算出的流是 expand 第一个参数与第二个参数的商的小数点后无限位数
;; (num 需小于 den)

;; (expand 1 7 10)
;; (1 4 2 8 5 ...)
;; 1 / 7 = 0.14285...

;; (expand 3 8 10)
;; (3 7 5 0 0 ...)

3.59

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
;; a

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

(define (div-streams s1 s2)
  (stream-map / s1 s2))			;no 0 in s2

(define (integrate-series s)
  (define integers (integers-starting-from 1))
  (div-streams s integers))

;; b

(define exp-series
  (cons-stream 1 (integrate-series exp-series)))

(define sine-series
  (cons-stream 0 (integrate-series cosine-series)))

(define cosine-series
  (cons-stream 1 (integrate-series (scale-stream sine-series -1))))

3.60

1
2
3
4
(define (mul-series s1 s2)
  (cons-stream (* (stream-car s1) (stream-car s2))
	       (add-streams (scale-stream (stream-cdr s2) (stream-car s1))
			    (mul-series (stream-cdr s1) s2))))

3.61

1
2
3
4
(define (calculate-x s)
  (cons-stream
   1
   (scale-stream (mul-series (stream-cdr s) (calculate-x s)) -1)))

3.62

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
;; (load "./ 3.61.scm")

(define (div-series s1 s2)
  (if (= (stream-car s2) 0)
      (error "element 0 in the second stream")
      (scale-stream (mul-series
		     s1
		     (calculate-x (scale-stream s2 (/ 1 (stream-car s2)))))
		    (/ 1 (stream-car s2)))))

(define tan-series
  (div-series sine-series cosine-series))

流计算模式的使用

  1. 流方法极富有启发性,因为借助于它去构造系统时,所用的模块划分方式可以与采 用赋值、围绕着状态变量组织系统的方式不同,例如,我们可以将整个的时间序列 (或者信号)作为关注的目标,而不是去关注有关状态变量在各个时刻的值;
  2. 流的描述形式特别优美而又方便,因为整个状态序列就像一个数据结构一样,可以 通过一集统一的操作直接地随意使用;
  3. 将序列范型(即高阶过程 filter, map…)这一技术推广到无穷流,就可以写出一 些很不容易用循环表示的程序,因为要那样做,就必须对无穷集合做“循环”(根本 没法做的);

练习 3.63-3.76

3.63

1
2
3
4
5
6
7
;; 因为重复计算;
;; 不用局部变量 guesses,那么每次递归调用 (sqrt-stream x) 都会构造一个新的流;
;; 而且其每个元素都必须重新计算,无法使用前面构建的流的记忆性过程的缓存结果;

;; when replace the delay implementation
;; 如果 delay 的实现不使用 memo-proc, 不使用记忆性过程带来的优化;
;; 两个版本在效率方面没有差异。

3.64

1
2
3
4
5
6
7
(define (stream-limit s t)
  (let ((sub-s (stream-map - s (stream-cdr s))))
    (define (get-the-desired sub-streams stream)
      (if (< (abs (stream-car sub-streams)) t)
	  (stream-car stream)
	  (get-the-desired (stream-cdr sub-streams) (stream-cdr stream))))
    (get-the-desired sub-s (stream-cdr s))))

3.65

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(define (ln2-summands n)
  (cons-stream
   (/ 1.0 n)
   (stream-map - (ln2-summands (+ n 1)))))

(define ln2-stream
  (partial-sums (ln2-summands 1)))	;first way

(define ln2-stream-euler
  (euler-transfrom ln2-stream))		;second way

(define ln2-stream-accelerated
  (accelerated-sequence euler-transfrom ln2-stream)) ;third way

3.66

1
;; 放弃

3.67

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(define (pairs s t)
  (cons-stream
   (list (stream-car s) (stream-car t))
   (interleave
    (interleave
     (stream-map (lambda (x) (list x (stream-car t)))
		 (stream-cdr s))
     (stream-map (lambda (x) (list (strem-car s) x))
		 (stream-cdr t)))
    (pairs (stream-cdr s) (stream-cdr t)))))

3.68

1
2
3
4
;; 那么对 pairs 过程体 (interleave p1 p2) 的求值
;; 将会立即对其第二个参数表达式即另一个 pairs 过程调用求值;
;; 因为传入的流是无穷流,这样的递归归调用永远不会停止;
;; 最后解释器报错:exceeds maximum recursion depth

3.69

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
(define (triples s t u)
  (cons-stream
   (list (stream-car s) (stream-car t) (stream-car u))
   (interleave
    (stream-map (lambda (x) (cons (stream-car s) x))
		(stream-cdr (pairs t u)))
    (triples (stream-cdr s)
	     (stream-cdr t)
	     (strem-cdr u)))))

(define desired
  (let ((base-stream (triples integers integers integers)))
    (stream-filter
     (lambda (x) (= (square (caddr x)) (+ (square (car x)) (square (cadr x)))))
     base-stream)))

3.70

 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
(define (merge s1 s2)
  (cond ((stream-null? s1) s2)
	((stream-null? s2) s1)
	(else
	 (let ((s1car (stream-car s1))
	       (s2car (stream-car s2)))
	   (cond ((< s1car s2car)
		  (cons-stream s1car (merge (stream-cdr s1) s2)))
		 ((> s1car s2car)
		  (cons-stream s2car (merge s1 (stream-cdr s2))))
		 (else
		  (cons-stream s1car
			       (merge
				(stream-cdr s1)
				(stream-cdr s2)))))))))

(define (merge-weighted s1 s2 weight)
  (cond ((stream-null? s1) s2)
	((stream-null? s2) s1)
	(else
	 (let ((s1car (stream-car s1))
	       (s2car (stream-car s2)))
	   (cond ((< (weight s1car) (weight s2car))
		  (cons-stream s1car (merge-weighted (stream-cdr s1) s2 weight)))
		 ((> (weight s1car) (weight s2car))
		  (cons-stream s2car (merge-weighted s1 (stream-cdr s2) weight)))
		 (else
		  (cons-stream s1car
			       (merge-weighted
				(stream-cdr s1)
				s2 weight))))))))

(define (weighted-pairs s t weight)
  (cons-stream
   (list (stream-car s) (stream-car t))
   (merge-weighted
    (stream-map (lambda (x) (list (stream-car s) x))
		(stream-cdr t))
    (weighted-pairs (stream-cdr s) (stream-cdr t) weight)
    weight)))

;; a

(define desired1
  (weighted-pairs
   integers
   integers
   (lambda (p) (+ (car p) (cadr p)))))

;; b

(define (weighted235 p)
  (+ (* 2 (car p)) (* 3 (cadr p)) (* 5 (car p) (cadr p))))

;; in Chinese version of SICP, i or j must be fully divided by 2, 3 or 5
(define s (cons-stream 1 (merge (scale-stream s 2)
				(merge (scale-stream s 3)
				       (scale-stream s 5)))))

(define desired2
  (weighted-pairs
   s
   s
   weighted235))

3.71

 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
(define (cube-pair p)
  (+ (expt (car p) 3) (expt (cadr p) 3)))

(define base-stream
  (weighted-pairs
   integers
   integers
   cube-pair))

(define (Ramanujan-pairs s1 s2)
  (if (= (cube-pair (stream-car s1))
	 (cube-pair (stream-car s2)))
      (cons-stream (cube-pair (stream-car s1))
		   (Ramanujan-pairs (stream-cdr s1)
				    (stream-cdr s2)))
      (Ramanujan-pairs (stream-cdr s1)
		       (stream-cdr s2))))

(define Ramanujan-streams
  (Ramanujan-pairs base-stream
		   (stream-cdr base-stream)))

;; 1729
;; 4104
;; 13832
;; 20683
;; 32832
;; 39312

3.72

 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
(define (square-pair p)
  (+ (square (car p)) (square (cadr p))))

(define base-stream
  (weighted-pairs
   integers
   integers
   square-pair))

(define (desired-pairs s1 s2 s3)
  (if (= (square-pair (stream-car s1))
	 (square-pair (stream-car s2))
	 (square-pair (stream-car s3)))
      (cons-stream (list (square-pair (stream-car s1))
			 (stream-car s1)
			 (stream-car s2)
			 (stream-car s3))
		   (desired-pairs (stream-cdr s1)
				  (stream-cdr s2)
				  (stream-cdr s3)))
      (desired-pairs (stream-cdr s1)
		     (stream-cdr s2)
		     (stream-cdr s3))))

(define desired-streams
  (desired-pairs base-stream
		 (stream-cdr base-stream)
		 (stream-cdr (stream-cdr base-stream))))

;; (325 (1 18) (6 17) (10 15))
;; (425 (5 20) (8 19) (13 16))
;; ...

3.73

1
2
3
4
5
6
7
8
(define (RC r c dt)
  (define (voltage i v0)
    (add-streams
     (scale-stream i r)
     (integral (scale-stream i (/ 1 c))
	       v0
	       dt)))
  voltage)

3.74

1
2
3
4
5
(define zero-crossings
  (stream-map
   sign-change-detector
   sense-data
   (cons-stream 0 sense-data)))

3.75

1
2
3
4
5
6
(define (make-zero-crossings input-stream last-value last-avpt)
  (let ((avpt (/ (+ (stream-car input-stream) last-value) 2)))
    (cons-stream (sign-change-detector avpt last-avpt)
		 (make-zero-crossings (stream-cdr input-stream)
				      (stream-car input-stream)
				      avpt))))

3.76

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(define (smooth input-stream)
  (stream-map (lambda (i j) (/ (+ i j) 2))
	      (cons-stream 0 input-stream)
	      input-stream))

(define (make-zero-crossings input-stream smooth)
  (let ((smooth-stream (smooth input-stream)))
    (stream-map sign-change-detector
		smooth-stream
		(cons-stream
		 0
		 smooth-stream))))

流和延时求值

  1. 显式使用 delay 过程使参数变为 延时参数, 我们可以在只有参数的一部分信息 的情况下,开始生成输出流的有关部分;
  2. 显式使用 delay 和 force 能够提供很大的编程灵活性,但这种做法也可能导致程 序程序变得更加复杂;
  3. 变动性(赋值,变动对象)和延时求值(延时参数,流)在程序设计语言里结合得 非常不好,不要尝试写出混杂这两种风格的代码;

练习 3.77-3.80

3.77

1
2
3
4
5
6
7
8
9
(define (integral delayed-integrand initial-value dt)
  (cons-stream initial-value
	       (let ((integrand (force delayed-integrand)))
		 (if (stream-null? integrand)
		     the-empty-stream
		     (integral (dealy (stream-cdr integrand))
			       (+ (* dt (stream-car integrand))
				  initial-value)
			       dt)))))

3.78

1
2
3
4
5
(define (solve-2nd a b dt y0 dy0)
  (define y (integral (delay dy) y0 dt))
  (define dy (integral (delay ddy) dy0 dt))
  (define ddy (add-streams (scale-stream dy a) (scale-stream y b)))
  y)

3.79

1
2
3
4
5
(define (solve-2nd f y0 dy0 dt)
  (define y (integral (delay dy) y0 dt))
  (define dy (integral (delay ddy) dy0 dt))
  (define ddy (stream-map f dy y))
  y)

3.80

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(define (RLC r l c dt)
  (lambda (vc0 il0)
    (define vc (integral (delay dvc) vc0 dt))
    (define il (integral (delay dil) il0 dt))
    (define dvc (scale-stream il (- (/ 1 c))))
    (define dil
      (add-streams
       (scale-stream vc (/ 1 l))
       (scale-stream il (- (/ r l)))))
    (stream-map cons vc il)))

函数式程序的模块化和对象的模块化

  1. 引进赋值的主要收益就是使我们可以增强系统的模块化,把一个大型系统组织为一 集带有局部状态的独立对象,流模形可以提供等价的模块化,同时又不必使用赋值;
  2. 流的理论松开了被模拟的世界里的时间与求值中事件发生的顺序之间的紧密联系;
  3. 函数式程序设计语言不提供赋值或者变动对象,所有的过程实现的都是它们的参数 上的定义良好的数学函数,其行为不会变化,函数式途径对于处理并发系统特别有 吸引力,但是在处理类似交互式系统的时候,时间有关的问题也潜入了函数式模形 中,带来了非确定性的问题;
  4. 可以将这一世界模拟为一集相互分离的、受时间约束的、具有状态的相互交流的对 象;也可以将它模拟为单一的、无时间无状态的统一体;每种观点都有其强有力的 优势,但就其自身而言,又没有一种方式能够完全令人满意。

练习 3.81-3.82

3.81

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
;; order-stream (('generate) ('reset 124) ...)

(define (random-numbers order-stream val)
  (define (base-stream base)
    (cons-stream
     base
     (random-numbers
      (stream-cdr order-stream)
      (rand-update base))))
  (cond ((eq? (car (stream-car order-stream)) 'generate)
	 (base-stream val))
	((eq? (car (stream-car order-stream)) 'reset)
	 (base-stream (cadr (stream-car order-stream))))
	(else "Invalid order" (stream-car order-stream))))

3.82

 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
(define (monte-carlo experiment-stream passed failed)
  (define (next passed failed)
    (cons-stream
     (/ passed (+ passed failed))
     (monte-carlo
      (stream-cdr experiment-stream) passed failed)))
  (if (stream-car experiment-stream)
      (next (+ passed 1) failed)
      (next passed (+ failed 1))))

(define (rand-numbers a1 a2)
  (cons-stream
   (random-in-range a1 a2)
   (rand-numbers a1 a2)))

(define (make-point x y)
  (cons x y))

(define (x-point p)
  (car p))

(define (y-point p)
  (cdr p))

(define (mid-point p1 p2)
  (let ((x (/ (+ (x-point p1) (x-point p2)) 2))
	(y (/ (+ (y-point p1) (y-point p2)) 2)))
    (make-point x y)))

(define (radius-square p1 p2)
  (let ((x1 (x-point p1))
	(x2 (x-point p2)))
    (square (abs (/ (- x1 x2) 2)))))

(define (p r1 r2 m1 m2 rs)
  (not (> (+ (square (- r1 m1))
	     (square (- r2 m2)))
	  rs)))

(define (random-in-range low high)
  (let ((range (- high low)))
    (+ low (random range))))

(define (estimate-integral x1 x2 y1 y2)
  (define x-random-stream (rand-numbers x1 x2))
  (define y-random-stream (rand-numbers y1 y2))
  (define p1 (make-point x1 y1))
  (define p2 (make-point x2 y2))
  (define center (mid-point p1 p2))
  (define m1 (x-point center))
  (define m2 (y-point center))
  (define rs (radius-square p1 p2))
  (define (integral-test r1 r2)
    (p r1 r2 m1 m2 rs))
  (define results-stream
    (stream-map
     integral-test
     x-random-stream
     y-random-stream))

  (monte-carlo results-stream 0 0))