However, there's a special trick that can make the short code also efficient.

The idea is that we want to avoid computing the tail of a sequence until

that tail is needed for the evaluation result, and of course, that might be

never. That idea is implemented in a new class

which is called the stream. Streams are similar to lists but their

tail is evaluated only on demand. So, here's how it can define streams.

They are, can be built from a constant Stream.empty, so the empty value and the

stream object, and a constructor Stream.cons.

So to build a stream that consists of the number one and two, you could write

Stream.cons one, Stream.cons two, Stream.empty.

And, of course, streams can also be defined like the other collections by

using the stream object as a factory, So you could write simply stream of one,

two, three. A third way to produce streams is with a

toStream method, which can be applied to any collection to produce a stream.

So in example, you see here, we have a range one to 1000 and turn it into a

stream with a toStream method. It's interesting to see what the result of

that call is so what we see here is a stream of Int, which is written like this.

It's a, it's a Stream(1, ).? What that mean is that a stream is

essentially a, recursive structure like a list so we have a one here, but the tail

is not yet evaluated, so that's why the interpretor worksheet has printed a

question mark here. The tail will be evaluated if somebody

wants to know explicitly. Let's look at stream evaluation in a

little bit more detail using ranges as an example.

Instead of the usual range and then toStream expression, I have decided that I

wanted to work from first principles and I wrote a streamRange function directly

here. So that's the usual recursive function, if

the lower bound is greater or equal to higher bound, I return the empty stream,

Otherwise, it's a cons of the lower bound and a recursive call of streamRange(lo +

one, Hi)). If you compare that to the function that

does the same thing for lists, here is listRange and turns out that the two

functions are actually completely isomorphic.

They have exactly the same structure, Only one returns a stream, the other

returns a list, And the empty stream here corresponds to

the Nil case for the lists and the cons case for the streams corresponds to the

cons operator for the lists. Yet their operational behavior is

completely different. If we look at listRaange first, what would

that function do if we have listRange (one, ten).

What would the thing do? Well, it would create a list with the one

here and so on, Until I have the ten here and I have a

Nil, So it would generate the complete list in

one go. Whereas for a streamRange, what would

happen is it would create a first cons pair with a one here and then the rest

would be a ?. So it wouldn't be generated, instead,

there would be an object that would know how to reconstitute the stream if somebody

demands it. If I take the tail then of this

streamRange result, Then, somebody wants to know I would

create the second element of the stream and the third element would have a ?,

And so on until potentially somebody forces wants to know all the elements in

this stream, in which case, in the end, I would have the same structure as for the

lists. But before I would have partially constructed streams, it always end in,

essentially the ?, which stands for unevaluated stream.

In most respects, Streams are actually like List, in particular, Streams support

almost all the methods of a List. So for instance, to find the second prime

number between 1000 and 10,000, the problem we started with, it could simply

write it this way, so instead of writing the range directly, we convert the range

to a string, then we apply the filter method on stream and we apply the apply

method on a stream with the one as the argument.

There's only one exception where streams don't follow list and that's the cons

operator. So if you write x :: xs, that's always

produces a list, never a stream. But there is an alternative operator which

is written #, :: which produces a stream. So x.

#, :: xs is actually the same as Stream.cons (x, xs),

And that operator can be used in expressions, as you see here, but also in

patterns.. So let's look at the implementation of

Streams. It's actually quite close to the

implementation of Lists. So let's start with a base trait, there's

a trait Stream[+A] and it extends a Seq[A] just like lists do, and it has the same

fundamental operations as lists, namely isEmpty, head and tail.

And again as for lists all other methods can be defined in terms of these three

fundamental ones. So if we look at complete implementation

of Streams than actually these also follow closely the ones for lists.

There's one difference, however, that for Streams, the economical implementations

are defined as members of the Stream object, so that's why we write

Stream.empty, which corresponds to list Nil and Stream.cons,