, In this segment and the next, we're going to talk about streams which are a
different programming idiom that also needs some notion of delaying evaluation
and the implementation will use thunks. In order to accomplish that. So, a stream is
a word that we use in computer science to mean an infinite sequence of values. All
right, something that can go on for as long as you need. It behaves like
somethings infinitely big. Now, one thing about infinite sized things, you can't
actually make them. Right? We need something that's going to represent
something that could go on forever. And the key idea we're going to use is to use
a thunk to delay the evaluation of most of the sequence and only generate some prefix
of the sequence That some other computation needs. We're not going to need
any new language constructs. This is just a program idiom using thunks and things
like that. But it's a powerful concept for dividing labor up in a way that works in a
lot of different software systems. So, the idea is that. The, the program, part of
the program producting the stream, creating the stream, knows how to create
any number of values you need. But does not know how many you need. Whereas the
stream consumer can ask for these values as it goes along, without knowing anything
about the process that is generating So it turns out this comes up a lot in software
systems. It's okay if you're not familiar with any of these examples, but I thought
I would mention them for those of you who are. one way is if you're implementing
code that need to respond to a whole bunch of user events, mouse clicks, keyboard
presses, things like that, we saw earlier in the course we could do that will call
backs. But another way to do it is to think of that as some stream of events.
We'll as for each one as we need it, and then we'll compute some result with that
thing so far. And someone else will generate those events as they occur. Have
you ever programmed with pipes in the UNIX shell system? It turns out that the second
c ommand, pulls data from the first command as it needs it so it views the
first command as a stream. And the first command's output is generating that
stream. There's also a nice connection with electrical engineering and circuits,
that if you think of a timed circuit with feedback. You can think of the different
output values it's sending on it's output wires as forming an infinitely long
sequence. And then the, the circuits reading those values Can read the ones
that they're interested in. Anyway, just optional things just showing this is kind
of, of a universal concept even if you find it a bit abstract. You'll also see
some simpler and more fun examples on the homeowrk assignment associated with this
material. Okay, so we want to represent a stream. In some way that we don't actually
generate an infinitely long list or something like that. So here's how we will
do it. We're going to represent a stream as a thunk. So a stream will just be a
thunk, but not just any kind of thunk, a thunk that, when you call it gives back a
pair, where the car is the next thing in the sequence. The first thing in the
sequence. And the CDR, is a stream for values 2 through infinity. So it is a
stream that, if you use it, you get the next value. So in this segment, I'm just
going to show you how to use this thing Then in the next segment, we'll see how to
define our own. Using them usually helps explain what they are and get a little
better sense before we try to create them. So I've already loaded the file where I've
created the streams I will show you in the next segment. And one of the streams is
the infinite sequence of powers-of-two. Two.
So you know, the first thing this thing returns - I, I forget if it starts at 1 or
2. And the 4, and then 8, and then 16, and then 32, on forever. Because we don't know
how many powers of 2 we need. But when I said powers-of-two, as you saw here, all I
got back was a procedure. Because our streams are thunks. That when you call
them return a pair. So, how do you call a thunk? You put it in parentheses,
powers-of-two. And look at that. I got back a pair, who's first component is 2,
so I did set it up to start at 2, and a second component is another procedure.
Turns out that's a thunk. So, if I wanted the first thing in the sequence, I could
just say car. Of calling that. And if I wanted the second sequence. Let's think
about this. Need to call the cutter to get another stream, a stream is a thunk so I
need to call it. And then I need the car of that. And that gets me four. What if I
wanted the next element of the sequence? Well this thing is four, the cdr is
another stream, a stream is a thunk so you call it. That gives back a pair and I made
the car. of that. And that would give me 8. Now of course we
wouldn't keep programming like this to get 16 or 32. The idea is we would have some
sort of recursive function that is passing this next stream onto say some recursive
call. And then we. Apply that string, to get a pair, and we take car to get the
next thing. And so if you wanted to say add up the first 100 powers of 2, you
would just have some little recursive function, that would be using this string
as you go along. So what I thought I would do instead of showing you that is show you
something even more general. Let's define a recursive function that I'll call number
until. That's going to take in a stream. And the function which I'll call tester,
alright, and the idea of what this is going to do is it's going to count how
many stream elements you need to process before tester returns true for the first
time. So if it doesn't ever returns true, we're going to an infinite loop. But
otherwise, we'll stop as soon as we get our first true and we'll return the count
of how many we've got. So I'm going to do this with a little tail recursive helper
function. So I have a little letrec here. So I'm going to take in my current stream,
the stream that has all the elements I haven't processed so, So far, my
accumulator which is my answer so far, I'm goi ng to have to put some stuff in here.
and then I'm just going to call F the stream I started with, and 1, that's my
initial accumulator. So now the idea is all the body of F has to do, where I've
left this dot, dot, dot. is see well, what does tester have to say on the first
element of the stream. If that's true, return answer. Ohterwise, call f again
with one more and with the tail, if you will of the stream. The rest of the
values. So here's how I'm going to do this. Let's first of all call that stream.
We know a stream is a thunk. All right? I'll get rid of these so you can see it.
So I know a stream is a thunk. So if I call it, I should get back this pair.
Right, of the first element, and then the stream that is the rest of the elements.
'Kay. Now that I have that pair, let's call
tester on the car. If I get back true, I'm done. Return ans.
All right? Otherwise, call f. On the cdr of the pair, that's my new stream, right?
Don't call it yet, right? We don't want a pair, right? This would be a pair. But f
expects the stream and then f itself calls that thunk to get back the pair. So just
with the cdr and then one more for the accumulator. That's my call to f. That
finishes my if, my let, my definition of the lambda for f, and this letrec that I
then to use to start by calling f with the stream that was passed a number, until.
End 1. So, and then I need to end my define, my letrec, and my define, and now
I've just defined a function, that's all I did. Okay.
So now if I call number-until with the stream powers of 2. Alright? And how about
a little function that takes in a number and says, does that number equal 16? I go
back to 4. It took me four times through the stream until I got something that
equaled 16. Let's have a little more fun. How about I keep going until I get a
number that is bigger than This number. Ok? The great thing about powers of two is
they grow really fast, so that took 339 tries. If I had come up with a number ten
times bigger it'll take 343 tries, and ten times better than that it'll probably take
346 tries. because powers-of-two multiply really fast. Let me point out that when
you program your streams, you tend to make lots of mistakes with parenthesis. Think
very carefully about, do I want to pass in a stream or a pair that I get back when I
call the thunk? If I get this wrong and I put parenthesis here And are passing to
number until a pair. And therefore right here you can just see at the top of the
screen. That's going to be pair when you try to treat a pair as a function, you get
a big nasty error function. It says, procedure obligation expected procedure
given the pair two and some string. So you have to think very carefully about the
difference between a thunk and a pair. And if you do that, you can do some beautiful
programming by using streams with beautiful recursive functions to get
interesting results.