随机惰性流:评估何时发生?

Lazy stream of random(s): When does evaluation happen?

我认为以下代码应该定义 1 到 10 之间的随机数流:

(define random-stream (stream-cons (random 1 11) random-stream))

然而,它实际上所做的是定义一个特定随机数的流。例如:

> (stream->list (stream-take random-stream 10))
'(5 5 5 5 5 5 5 5 5 5)

我认为这是 (random 1 11) 在首次解析定义时生成的随机数。我通过使 random-stream 成为无参数函数来解决这个问题:

(define (random-stream) (stream-cons (random 1 11) (random-stream)))

这个有效:

> (stream->list (stream-take (random-stream) 10))
'(6 1 10 9 4 2 2 3 3 10)

所以在我看来,可以理解的是,常量是在读取时求值的,而函数是在调用时求值的。通常这无关紧要,但在流的情况下——你有一个递归定义——这会有所不同。

这是它的工作原理,还是比这更微妙?关于这种差异,还有其他情况应该注意吗?

使 random-stream 成为无参数函数是正确的解决方案。

(define (random-stream) (stream-cons (random 1 11) (random-stream)))

我会解释为什么。

当您使用 (define my-stream (stream-cons ....)) 正常定义流时,流只有一个值。对 my-stream 的任何引用都将产生相同的值。

(define my-stream (stream-cons (random 1 11) my-stream))

"rest" 中的 my-streammy-stream.

的值 eq? 完全相同
> (eq? my-stream (stream-rest my-stream))
#true

因为它们是相同的值,所以它们可以在函数调用中被替换。如果 (stream-first my-stream) returns 5,则 (stream-first (stream-rest my-stream)) 也必须 return 5。(这是因为 stream-first 是一个 "pure" 函数它return相同输入的相同输出。)

> (eq? (stream-first my-stream) (stream-first (stream-rest my-stream)))
#true

函数版本不是这种情况,因为每次调用函数时都会创建一个新的流值。

(define (random-stream) (stream-cons (random 1 11) (random-stream)))

> (eq? (random-stream) (random-stream))
#false
> (eq? (stream-first (random-stream)) (stream-first (random-stream)))
#false

由于"rest"字段也调用了(random-stream),其余与整体不同

> (define generated-stream (random-stream))
> (eq? generated-stream (stream-rest generated-stream))
#false
> (eq? (stream-first generated-stream) (stream-first (stream-rest generated-stream)))
#false

我同意另一个答案,即 OP 代码的问题在于 random-stream 是一个流,其中 (stream-first random-stream) 是一些随机数,而 (stream-rest random-stream) 也是同一个流以相同的数字开头。

虽然我不太同意“无参数函数是正确的解决方案”。

另一种解决方案是使用 stream-map 将随机数映射到自然数上:

(define random-stream/1-10
  (stream-map (lambda (x) (random 1 11)) (in-naturals)))

创建一个生成随机数流的函数甚至更好:

(define (random-stream a b)
  (stream-map (lambda (x) (random a b)) (in-naturals)))

这个函数可以用来创建流(注意in-naturals也是一个创建流的函数):

random_streams.rkt> (define my-stream (random-stream 1 11))
random_streams.rkt> (stream->list (stream-take my-stream 10))
'(1 1 2 7 5 7 4 2 2 9)

使用这个创建流的函数的想法,可以挽救stream-cons方法:

(define (random-stream-cons a b)
  (stream-cons (random a b) (random-stream-cons a b)))

当在使用 random-stream-cons 创建的流上调用 stream-first 时,将 returned 一个随机数;当在同一个流上调用 stream-rest 时,另一个以随机数作为其第一个元素的流被 returned.

创建的流是持久的:

random_streams.rkt> (stream->list (stream-take random-stream/1-10 10))
'(10 9 9 1 2 7 6 2 6 6)
random_streams.rkt> (stream->list (stream-take random-stream/1-10 15))
'(10 9 9 1 2 7 6 2 6 6 10 1 2 8 5)

random_streams.rkt> (define my-stream-1 (random-stream 1 11))
random_streams.rkt> (stream->list (stream-take my-stream-1 10))
'(1 4 1 10 7 9 9 9 2 9)
random_streams.rkt> (stream->list (stream-take my-stream-1 15))
'(1 4 1 10 7 9 9 9 2 9 2 3 9 9 10)

random_streams.rkt> (define my-stream-2 (random-stream-cons 1 11))
random_streams.rkt> (stream->list (stream-take my-stream-2 10))
'(10 4 6 1 4 2 10 5 3 6)
random_streams.rkt> (stream->list (stream-take my-stream-2 15))
'(10 4 6 1 4 2 10 5 3 6 1 5 7 5 5)

random-stream-cons/1-10 函数与早期的 random-stream-cons 函数基本相同(但没有参数);然而它们都不是 streams。它们都是 create 流的函数:

(define (random-stream-cons/1-10) (stream-cons (random 1 11) (random-stream-cons/1-10)))

每次调用其中一个流创建函数时,都会return编辑一个新流:

random_streams.rkt> (stream->list (stream-take (random-stream-cons/1-10) 10))
'(10 8 3 10 8 8 1 8 4 5)
random_streams.rkt> (stream->list (stream-take (random-stream-cons/1-10) 10))
'(1 8 7 3 8 2 2 10 6 5)

这可能正是我们所需要的;这些函数非常有用,例如,在迭代上下文中:

random_streams.rkt> (for ([x (stream-take (random-stream 1 11) 5)])
                      (displayln x))
2
8
9
1
3

因此,return 流的函数很有用,如果需要,可以将生成的流绑定到符号。对于可能需要多次使用不同值的流,可以在自定义流创建函数中提供参数。但是对于一次性流,stream-map 已经完成了 returning 一个流的工作,它可以像 OP 最初写的那样绑定到一个符号。