Search code examples
randomstreamschemeracketlazy-evaluation

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


The following code, I thought, should define a stream of random numbers between 1 and 10:

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

However, what it actually does is define a stream of a specific random number. For example:

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

I presume this is the random number that (random 1 11) produces when the definition is first parsed. I got around this by making random-stream an argument-less function:

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

This works:

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

So it looks to me that constants are, understandably, evaluated at read time, whereas functions are evaluated at call time. Usually this wouldn't matter, but in the case of a stream -- where you've got a recursive definition -- this makes a difference.

Is this how it works, or is it more subtle than this? Are there other cases one should be aware of regarding this difference?


Solution

  • Making random-stream an argument-less function is the correct solution.

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

    I will explain why.

    When you define a stream normally with (define my-stream (stream-cons ....)), there is only one value for the stream. Any reference to my-stream will produce the same value.

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

    The my-stream inside the "rest" is literally the same value eq? to the one my-stream.

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

    So because they are the same value, they can be substituted in function calls. If (stream-first my-stream) returns 5, then (stream-first (stream-rest my-stream)) must also return 5. (This is because stream-first is a "pure" function in the sense that it returns the same output for the same inputs.)

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

    This is not the case with the function version because every time the function is called it creates a new stream value.

    (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
    

    Since the "rest" field also calls (random-stream), the rest is different from the whole.

    > (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