方案:使用`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
。
这是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
。