sicp 4.2.2小节部分习题
程序员文章站
2022-03-11 07:50:24
...
4.27,
<!---->;;; L-Eval input:
(define count 0)
;;; L-Eval value:
ok
;;; L-Eval input:
(define (id x)
(set! count (+ 1 count))
x)
;;; L-Eval value:
ok
;;; L-Eval input:
(define w (id (id 10)))
;;; L-Eval value:
ok
;;; L-Eval input:
count
;;; L-Eval value:
1
;;; L-Eval input:
w
;;; L-Eval value:
10
;;; L-Eval input:
count
;;; L-Eval value:
2
(define count 0)
;;; L-Eval value:
ok
;;; L-Eval input:
(define (id x)
(set! count (+ 1 count))
x)
;;; L-Eval value:
ok
;;; L-Eval input:
(define w (id (id 10)))
;;; L-Eval value:
ok
;;; L-Eval input:
count
;;; L-Eval value:
1
;;; L-Eval input:
w
;;; L-Eval value:
10
;;; L-Eval input:
count
;;; L-Eval value:
2
至于原因,w在没有强迫求值前,仅仅执行了一步(id 10),因此此时count为1,当要求打印w的时候force执行了第二步(id 10),因此count增加为2。
4.28,当参数也是函数的时候,例如:
<!---->(define square (lambda(x) (* x x)))
(define (test proc a)
(proc a))
(test square 3)
(define (test proc a)
(proc a))
(test square 3)
如果对operator不采用actual-value,那么square将延时求值,在执行(proc a)时无法辨认eval的过程类型。
4.29,俺第一个想到的就是树形递归的斐波那契数列:
<!---->(define (fib n)
(cond ((= 0 n) 0)
((= 1 n) 1)
(else
(+ (fib (- n 1)) (fib (- n 2))))))
不带记忆功能和带记忆功能的force-it之间的性能差距非常明显。(cond ((= 0 n) 0)
((= 1 n) 1)
(else
(+ (fib (- n 1)) (fib (- n 2))))))
第二问,有趣的地方在于square过程,注意到(define (square x) (* x x)),x在body出现了两次,那么如果是使用不带记忆功能的force-it, x将被求值两次,如果x本身带有副作用(例如例子里面的id过程),那么显然副作用也将被调用两次,因此答案不言自明。带记忆功能的force-it版本中,count将仍然是1,而在不带记忆功能的版本中count将增长到2。
4.30,第一问,我也谈不出所以然为什么ben的说法是正确的,关注下第二问的两个过程在不同eval-sequence下的表现,(p1 1)的结果没有改变都是(1 2),而(p2 1)在原始版本的eval-sequence中结果是1,而在Cy修改后的版本中(对中间步骤采用actual-value)结果是(1 2),也就是说在原始版本中的(set! x (cons x '(2)))的副作用根本没有实现,而在修改后的版本中实现了。俺觉的这个问题很迷惑,惰性求值与side effect相互作用很奇特,不过我更偏向原始版本,因为我觉的这样的实现更容易看清代码的意图,也就是说在透明性上更好,例如我分析p2过程就可以认为直接返回参数x;而实现副作用很容易让人掉入陷阱,并且很可能引进难以查找的bug。
上一篇: sicp 4.3.2部分习题
下一篇: 漂亮的代码