I came across the following example in Lecture 6B of the SICP lecture series, and need some help understanding how it is evaluated.
The example uses stream processing to create an infinite stream of integers greater than 1:
; utility procedure
(define (add-streams s1 s2) ; Returns a stream containing the element-wise sums of s1 and s2
(cond ((empty-stream? s1) s2) ; If either stream is empty, return the non-empty one
((empty-stream? s2) s1)
(else
(cons-stream (+ (head s1) (head s2)) ; Otherwise, return a new stream whose first element is the sum of the heads of s1 and s2
(add-streams (tail s1) (tail s2))))) ; ...and whose tail is the result of recursively adding the streams in the tails of s1 and s2.
(define ones (cons-stream 1 ; infinite stream of 1s
ones))
(define integers (cons-stream 1 ; infinite stream of increasing integers, starting from 1
(add-streams integers ones)))
I understand that integers is built up by incrementing the head of the stream returned by successive add-streams calls, but I'm not quite sure if I understand how it is evaluated.
integers is the stream of all integers greater than 1, so we can access the 3rd integer in this set as follows:
(head (tail (tail integers)))
The innermost tail forces the (add-streams integers ones) promise to evaluate:
(add-streams integers ones) -----> (cons (+ (head integers) (head ones))
(delay (add-streams (tail integers) (tail ones))))
-----> (cons 2
(delay (add-streams (tail integers) (tail ones))))
The outer tail call then forces the (add-streams (tail integers) (tail ones)) promise to evaluate:
(add-streams (tail integers)
(tail ones)) -----> (add-streams (cons 2 (delay ...))
(cons 1 (delay ...)))
-----> (cons (+ (head (cons 2 (delay ...)))
(head (cons 1 (delay ...))))
(delay (add-streams (tail (cons 2 (delay ...)))
(tail (cons 1 (delay ...))))))
-----> (cons 3
(delay (add-streams (cons 3 (delay ...))
(cons 1 (delay ...)))))
When we call head on the result of the outer tail call, we get 3, which is the expected answer.
However, I'm not sure if I've evaluated this correctly. I'm especially unsure about the (tail integers) call that shows in the second reduction step. I reduce it to (add-streams (cons 2 (delay ...)) (cons 1 (delay ...))) in the third reduction step, is this correct?
I think your summary of what happens is correct. However I also think that the best way to think about this is one of two things. The first may be less helpful than the second which is why I am putting it first.
Either (1) think about the semantics of the thing and don't worry too much about evaluation.
In this case the right way to think about this is to think that:
1and whose tail is the stream of ones;0(or1if you want your natural numbers to begin at1) and whose tail is the stream of integers with1added to each one.Or (2), write an implementation of streams which you can then persuade to tell you what is going on.
Here is such an implementation. I apologise that this is written in Racket, not pure Scheme. In this implementation
delayis calledpostponeandforceis calledensure: this was done so that it an sit alongside any native implementation of these things. You can control:Note in particular that streams are not represented as conses: they are represented as procedures. So there is no punning between conses and streams: streams are a whole other thing. Note also that
cons-streampostpones both its arguments.Given this we can write
add-streams, just renaming things slightly from the question:And now define
onesandnaturals(again, notintegers):And now, finally, you can get it to show you what is going on: