I got the impression that people were not really comfortable with
streams, and especially with stream-get.  So I've written up some more
notes on streams.  I hope you will find it useful.  --Prof. Wand

****************************************************************


                           More on Streams



When we say (cons Exp1 Exp2), Exp1 and Exp2 are evaluated before the cons
returns.  So we can't use cons to create infinite lists.  For example,
you might think of writing something like

(define ints-from
  (lambda (n)
    (cons n (ints-from (+ n 1)))))

to create the list of all the integers starting from n, but this
doesn't work:  it goes into an infinite loop.

Instead, let's build a LAZY cons.  A lazy cons is going to be like a
cons, except it doesn't evaluate its arguments when it's built.  It
only evaluates them when somebody asks it for its car or cdr.

This may sound mysterious, but it's really easy.  Instead of building
a cons, just use a cons-thunk:  a thunk that returns a cons.

If I write

(lambda () (cons Exp1 Exp2))

Exp1 and Exp2 are not evaluated now-- instead, you get a procedure
which, WHEN INVOKED, evaluates its body, including the Exp1 and Exp2.

An ordinary list is either '() or a cons whose cdr is a list.  So
a lazy list is a lazy cons whose (lazy) cdr is a lazy list.  

By this definition a lazy list is always infinite-- we can simulate finite
lazy lists by making everything after "the end" an end-marker:

(define null-lazy-list
  (lambda () 
    (cons end-marker
          null-lazy-list)))
        
And of course a stream is exactly the same thing as a lazy list.

Now we can write ints-from:

(define ints-from
  (lambda (n)
    (lambda ()
      (cons n (ints-from (+ n 1))))))

(ints-from 1) returns (lambda () (cons 1 (ints-from (+ 1 1))))  

((ints-from 1)) returns (cons 1 (ints-from 2))  :  a list whose car is
1 and whose cdr is the LAZY LIST of integers starting from 2.

So building lazy lists is easy:  to build a lazy list, you write

(lambda ()
  (cons the-car
        thunk-which-builds-the-cdr))

Of course, there may be considerable computation before you get the
car and cdr, but this computation won't be done until the thunk is
run.  You might even have side effects.  For example, we had this
example last time:

(define prompt-read
  (lambda (prompt)
    (printf "~d " prompt)
    (read)))

(define make-input-stream
  (lambda (prompt)
    (lambda ()
      (cons (prompt-read prompt)
            (make-input-stream prompt)))))

which makes a stream out of expressions read from the standard input.
Whenever you run the thunk [say, by writing ((make-input-stream  "input>")) ] 
prompt-read gets called, which reads from the input stream.

Now, you won't get anywhere until you know how to take a lazy list
apart.  A first try would be to define

(define lazy-car
  (lambda (lazy-list) (car (lazy-list))))

(define lazy-cdr
  (lambda (lazy-list) (cdr (lazy-list))))

In each case, you run the thunk, getting a cons-box, and then you take
either its car or cdr.  

But this won't get us far.  Remember that in an ordinary list, we do
the computation of its pieces once, when we do the cons (this may be hard);
but taking the pieces apart with car or cdr is easy.  In a lazy list, we've
reversed this:  we don't do ANY computation when we build the list-- all the
computation comes when we try to take the list apart by invoking the thunk.

So our definition of lazy-car and lazy-cdr are at best wasteful, because they
do all the computation in the thunk over and over again, every time you call
them.  Worse yet, if the computation involves side-effects (as in
make-input-stream), you may get different answers each time!

So we need to make sure that we invoke each thunk at most once.  Most likely,
you'll want to take both the car and cdr of this lazy list.  The only
reasonable way to do this is:

(let ((temp (lazy-list)))
  (let ((the-car (car temp))
        (the-cdr (cdr temp)))
    (..do something with the-car and the-cdr...)))

Here we run the thunk once by saying (lazy-list).  We then give names
to its pieces, and do whatever it is we really wanted to the pieces.

For example:

(define stream->list
  (lambda (stream end-marker-test)
    (let ((temp (stream)))
      (let ((head (car stream))
            (tail (cdr stream)))
        (if (end-marker-test head)
          '()
          (cons head (stream->list tail end-marker-test)))))))

If we write code using lazy lists, we will write this pattern of code
over and over again.  So we write a PROCEDURE that encapsulates it.

To do this we observe the following important fact:

(let ((var1 Exp1) (var2 Exp2) ...) Body)
is PRECISELY THE SAME as
((lambda (var1 var2 ...) Body)
 Exp1 Exp2 ...)

In both cases, we evaluate Exp1, Exp2, etc., and then we evaluate Body
in an environment in which var1, var2, etc., are bound to the values
of Exp1, Exp2, etc.

Think about this.  It is important.

For the case of streams, this says we can replace 

(let ((temp (lazy-list)))
  (let ((the-car (car temp))
        (the-cdr (cdr temp)))
    (..do something with the-car and the-cdr...)))

by 

(let ((temp (lazy-list)))
  ((lambda (the-car the-cdr)
     (..do something with the-car and the-cdr...))
   (car temp)
   (cdr temp)))

You can check that here, too, we run the thunk, bind its pieces to
the-car and the-cdr, and then do something with the-car and the-cdr.

This form can be abstracted as:

(let ((temp (lazy-list)))
  (some-function (car temp) (cdr temp)))

So we write

(define decompose-lazy-list
  (lambda (lazy-list fcn)
    (let ((temp (lazy-list)))
      (fcn (car temp) (cdr temp)))))

Using analyze-lazy-list, we can write stream->list as:

(define stream->list
  (lambda (stream end-marker-test)
    (decompose-lazy-list
      stream
      (lambda (head tail)
        (if (end-marker-test head)
          '()
          (cons head (stream->list tail end-marker-test)))))))

We can write stream transducers.  Here is a procedure that maps a function
down the elements of a stream (like "map" does on lists):

(define map-stream
  (lambda (fcn stream)
    ;; don't do anything now, just build the lazy list of answers:
    (lambda ()
      ;; if somebody asks, then do some computation
      (decompose-lazy-list stream
        (lambda (first rest)
          (cons first
                (map-stream fcn rest)))))))

Of course, the things we've built here are exactly the same as the
streams we built before:  make-lazy-stream is the identity function,
so it was just a marker reminding the reader that this thunk is
intended as a stream.  And stream-get is just the same as
decompose-lazy-list. 

[Digression:  The style of doing decompose-lazy-list is not restricted
  to streams.  We could just as easily write things like:

(define decompose-list
  (lambda (list fcn)
    (fcn (car list) (cdr list))))

(define remove1
  (lambda (a list)
    (if (null? list) '()
      (decompose-list list
        (lambda (the-car the-cdr)
          (if (eq? the-car 'a) the-cdr
            (remove1 a the-cdr)))))))

This automates the taking apart of the list into its car and cdr.  We
could even automate the testing for null:

(define list-case
  (lambda (list value-if-nil fcn)
    (if (null? list)
      value-if-nil
      (fcn (car list) (cdr list)))))

(define remove1
  (lambda (a list)
    (list-case list
      '()                               ; if it null, return nil
      (lambda (the-car the-cdr)         ; otherwise take it apart
        (if (eq? the-car 'a) the-cdr
            (remove1 a the-cdr))))))

But this is a different story....]


