方案:使用`delay`表达式时显示问题

Scheme: problem about display when using `delay` expression

这是SICP中ex3.51相关的问题,代码如下

(define (cons-stream x y)
  (cons x (delay y)))
(define (stream-car stream) (car stream))
(define (stream-cdr stream) (force (cdr stream)))
(define (stream-map proc s)
  (if (stream-null? s)
      the-empty-stream
      (cons-stream
       (proc (stream-car s))
       (stream-map proc (stream-cdr s)))))
(define (stream-enumerate-interval low high)
  (if (> low high)
      the-empty-stream
      (cons-stream
       low
       (stream-enumerate-interval (+ low 1) high))))
(define (stream-ref s n)
  (if (= n 0)
      (stream-car s)
      (stream-ref (stream-cdr s) (- n 1))))
(define (show x)
  (display x)
  x)
;test
(stream-map show (stream-enumerate-interval 0 10))

输出为 012345678910(0 . #<promise>)

但我认为 cons-stream 中的延迟表达式延迟了评估,如果我在 stream-map 中使用不同的处理函数,如 lambda (x) (+ x 1) 输出 (1 . #<promise>) 更合理,那么为什么 display 打印所有数字呢?

这个定义有问题:

(define (cons-stream x y)
  (cons x (delay y)))

它将 cons-stream 定义为一个函数,因为它使用 define

Scheme 的计算是 eager:在输入函数体之前对参数进行计算。因此 y 在传递给 delay.

时已经完全计算出来了

相反,cons-stream 应该定义为宏,如

(define-syntax cons-stream
  (syntax-rules ()
    ((_ a b) (cons a (delay b)))))

或者我们可以明确地、手动地调用 delay,例如

(define (stream-map proc s)
  (if (stream-null? s)
      the-empty-stream
      (cons
       (proc (stream-car s))
       (delay
          (stream-map proc (stream-cdr s))))))

那么在我们的代码中就不会调用 cons-stream,只有 (cons A (delay B)) 调用。并且 delay 一个宏(或特殊形式,无论如何),它在工作之前不评估其参数,而是直接操作参数表达式。

我们甚至可以放弃对 delay 的调用,并将 (cons A (delay B)) 替换为 (cons A (lambda () B))。这还需要重新实现 force(即 built-in,并与 built-in delay 一起)简单地 (define (force x) (x)) 或仅调用 (x) 在适当的地方手动强制流的尾巴。

您可以在 this answer, or an ideone entry (for this RosettaCode entry) 末尾看到这种基于 lambda 的流代码,而没有使用显式 lambda 的任何宏。不过,这种方法可以改变代码的性能,因为 delay 正在记忆,但基于 lambda 的流不是。如果我们尝试多次访问流的元素,就会看到差异。

另请参阅 this answer 以了解另一种流实现方式,手术式修改列表的最后一个 cons 单元格作为记忆 force