灯下 登录
计算机科学 / SICP / 4.2.2 An Interpreter with Lazy Evaluation

Exercise 4.30 · 习题

Exercise 4.30: Cy D. Fect, a reformed C
programmer, is worried that some side effects may never take place, because the
lazy evaluator doesn’t force the expressions in a sequence. Since the value of
an expression in a sequence other than the last one is not used (the expression
is there only for its effect, such as assigning to a variable or printing),
there can be no subsequent use of this value (e.g., as an argument to a
primitive procedure) that will cause it to be forced. Cy thus thinks that when
evaluating sequences, we must force all expressions in the sequence except the
final one. He proposes to modify eval-sequence from 4.1.1
to use actual-value rather than eval:

(define (eval-sequence exps env)
(cond ((last-exp? exps)
(eval (first-exp exps) env))
(else
(actual-value (first-exp exps)
env)
(eval-sequence (rest-exps exps)
env))))

Ben Bitdiddle thinks Cy is wrong. He shows Cy the for-each procedure
described in Exercise 2.23, which gives an important example of a
sequence with side effects:

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

He claims that the evaluator in the text (with the original
eval-sequence) handles this correctly:

;;; L-Eval input:
(for-each
(lambda (x) (newline) (display x))
(list 57 321 88))
57
321
88

;;; L-Eval value:
done

Explain why Ben is right about the behavior of for-each.

Cy agrees that Ben is right about the for-each example, but says that
that’s not the kind of program he was thinking about when he proposed his
change to eval-sequence. He defines the following two procedures in the
lazy evaluator:

(define (p1 x)
(set! x (cons x '(2))) x)

(define (p2 x)
(define (p e) e x)
(p (set! x (cons x '(2)))))

What are the values of (p1 1) and (p2 1) with the original
eval-sequence? What would the values be with Cy’s proposed change to
eval-sequence?

Cy also points out that changing eval-sequence as he proposes does not
affect the behavior of the example in part a. Explain why this is true.

How do you think sequences ought to be treated in the lazy evaluator? Do you

like Cy’s approach, the approach in the text, or some other approach?

练习 4.30:Cy D. Fect 是一位改学 Scheme 的 C 程序员,他担心某些副作用可能永远不会发生,因为惰性求值器不会强迫求值序列中的表达式。由于序列中除最后一个表达式之外,其他表达式的值都不被使用(这些表达式存在的目的仅在于其副作用,例如给变量赋值或打印输出),因此不会有后续操作(如作为基本过程的参数)来触发对它们的强迫求值。Cy 因此认为,在对序列求值时,必须强迫求值序列中除最后一个之外的所有表达式。他提议将 4.1.1 节中的 eval-sequence 修改为使用 actual-value 而非 eval:

(define (eval-sequence exps env)
(cond ((last-exp? exps)
(eval (first-exp exps) env))
(else
(actual-value (first-exp exps)
env)
(eval-sequence (rest-exps exps)
env))))

Ben Bitdiddle 认为 Cy 错了。他向 Cy 展示了练习 2.23 中描述的 for-each 过程,该过程是一个含有副作用的序列的重要例子:

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

他声称课文中的求值器(使用原始的 eval-sequence)能正确处理这个过程:

;;; L-Eval input:
(for-each
(lambda (x) (newline) (display x))
(list 57 321 88))
57
321
88

;;; L-Eval value:
done

请解释为什么 Ben 对 for-each 行为的判断是正确的。

Cy 同意 Ben 关于 for-each 例子的判断,但说那并不是他在提议修改 eval-sequence 时所考虑的那类程序。他在惰性求值器中定义了以下两个过程:

(define (p1 x)
(set! x (cons x '(2))) x)

(define (p2 x)
(define (p e) e x)
(p (set! x (cons x '(2)))))

在原始的 eval-sequence 下,(p1 1) 和 (p2 1) 的值分别是什么?在 Cy 提议修改后的 eval-sequence 下,这两个值又会是什么?

Cy 还指出,按他的提议修改 eval-sequence 不会影响第 a 部分例子的行为。请解释为什么。

你认为惰性求值器中应当如何处理序列?你更倾向于 Cy 的方案、课文中的方案,还是某种其他方案?

Racket #lang sicp
(define (eval-sequence exps env)
 (cond ((last-exp? exps)
 (eval (first-exp exps) env))
 (else
 (actual-value (first-exp exps)
 env)
 (eval-sequence (rest-exps exps)
 env))))