In this session, we cover lazy evaluation. Roughly, laziness means do things as late as possible, and never do them twice. We will apply laziness to streams, and trace how it helps evaluation in a concrete stream query. The implementation of streams that you've seen in the last session solves the problem of avoiding unnecessary computations when the tail value of the stream is not needed, but it suffers from another very serious potential performance problem. And that is that if tail is called several times the corresponding stream will be recomputed each time tail is called. And of course that could by itself cause up to exponential blow up in program complexity. Fortunately this problem can be avoided by storing the result of the first evaluation of tail and reusing the stored result instead of recomputing it the second and third times and all other times around. We can convince our self, that this optimization is sound, since the pure function of language and expression, produces the same result each time it is evaluated. So instead of re-evaluating the same expression several times. We could just squirrel away the first time we have produced the result, and reuse that result every time. That scheme is called lazy evaluation, as opposed to the call by name evaluation that we've seen in the last session. And also as opposed to the strict evaluation for normal parameters and wild definitions. Lazy evaluation is a very powerful principle because it avoids both unnecessary and repeated computations. In fact, it's so attractive that a programming language, Haskell, has been built on top of it. So Haskell uses lazy evaluation by default, everywhere. You could argue, well, why does Scala not do it? Well, there's one, or maybe two problems with lazy evaluation which are essentially rooted in the fact that lazy evaluation is quite unpredictable in when computations happen. And how much space they take. You could argue, in a abstract, pure functional language, it shouldn't really matter when computations happen, and that's true. But once you add mutable side effects, which Scala also permits, even though we haven't used them in this course, you can get into some very confusing situations. So what Scala does is it uses strict evaluation by default, like the absolute majority of all programming languages, but it still allows lazy evaluation of value definitions with the lazy val syntax form. So if you wrote lazy val x equals expression, you would get a lazy evaluation of the value x here. So what that means is that just in a call by name evaluation that you would get with def x equals expression. The expression here would not be evaluated immediately at the point of the finish, and it would be delayed, will be delayed until somebody wants the first time the value of x. But afterwards the behavior between def x and lazy val x diverge. For def x of course you have the behavior that every time you call x the expression is reevaluated, whereas for lazy val the expression is reused every time except for the first one. So let's test this understanding with an exercise. Consider the following program. We have a function expr and it defines three values X, Y and Z. Each definition is preceded by a print statement that prints that the definition is now evaluated. And then finally we have an expression that makes use of X, Y and Z, so it does Z plus Y, plus X plus Z, plus Y plus X. If you run this program, what gets printed as a side-effect of evaluating expr? Here you have some choices. Is it one of these four or maybe something else? So let's see how we would approach this problem. When we evaluate X, probably first have to evaluate the three definitions. We have a vowel definition here, the right-hand side gets evaluated immediately and would print an X. The lazy vowel on the def would not be evaluated at the point of definition, they would be delayed. Then we would get into the result expression where we first demand the value of Z, so that would print a Z. Then we demand the value of y so that would force the lazy valid would print the y. Then we would demand the value of x. This one is already evaluated, so nothing would be printed. Then we demand the value of z again, so that would give us another z of y again. Well, now y is evaluated. So we would just reuse the result we've evaluated the first time around. And finally the x is again evaluated. So the string that gets printed is x z y z. Lazy vowels, we can adopt our implementation of string.coms to make it more efficient. And the change is again very simple, the only thing that changed is that instead of a def tail equals tail, we use a lazy vowel tail equals tail. And that's all that's needed. With the changed it means that as before, we will evaluate tail only when it's first demanded. But unlike it was before, we will reuse the evaluation of tail every. Time after the first one so we will avoid the unnecessary repeated computation. So, all this avoiding of unnecessary computations looks really great, but maybe you're not yet convinced. How can we really be sure that our, execution will, in fact, avoid unnecessary portions of computations? Well, one way to be sure is to put it to a test using the substitution model. Using evaluation with our substitution model. Let's do that with the expression we started with. So stream range 1,000, 10,000, filter is prime, apply one. Let's start reducing that, and see what happens. So the first thing that happens here is that we have to expand string range. And here, I've given you the expanded definitions with the actual parameters replacing the former ones. The next thing that happens is that the if, then, else is evaluated. So that would give me the cons expression that we see here. Let's abbreviate this expression with the cons to C1, so what I would have is C1, filter is prime, apply, one. The next thing to do is, we need to expand the filter function. So, here you see it's definition, and then the rest that needs to be done is apply one on that. I have to evaluate the IF then ELSE, so C1 is definitely not empty, because it's a cons, so I would be left with this ELSE part of the first IF here. And I have to evaluate the head of the C1, the string, that would give me 1,000, because that's the first parameter past two counts. So I'm left with this expression here. Now the next thing to do is evaluating is prime. I'll leave that out, because we've done that already, but it's pretty clear that is prime of 1,000 should return false. So I replace the call by false. I evaluate the F which gives me the, this expression here. And I've evaluated the tail of the C1 constant. Now if you go back to the C1 constant what it was. It was a [inaudible] with the stream range expression. So when I evaluate the tail, that's what I will get. But what I'm left with is the expression string range of 1,001, 10,000. And then the same thing as filter is prime. Apply one. In other words, the same expression I started with, only instead of the 1,000, I have the 1,001 here. And that evaluation sequence continues until I hit the first prime number, which in this case would be 1,009. So this expression would expand by a sequence of reduction steps, to finally stream range 1,009, 10,000, filter is prime, apply one. I evaluate stream range again. Is the expression. And I want to abbreviate that expression to c2. So I'm left with c2 filters prime apply one. I evaluate the filter function on c2, and that gives me an, a sequence of expressions. Cons 1,009, and then this here, because 1,009 is a prime number, so it would be included in the result of filter. So the next thing to evaluate is the call of the apply function on this cons expression here. I've plugged in here the definition of apply, which I have given you below, it's the usual definition where a body would expect. So we are left with an expression, like this one here, which is an if then else to ask whether one equals zero, which is false. So that would simplify to the second part of the if then else, which you see here. Now, what we need to do is we need to evaluate tail. That would in turn, force the express tail part of these, this console. So we would get C2 tail at filter is prime, apply zero. The next thing to calculate, again, is the tail over here. So that now would give us the next stream range. The. Tail part of c2. Again, filter is prime apply zero. So what we see is we again, left with essentially, the expression we started with, only now we have. 1,010 here and we have zero here. Where we started with 1,000 on the left and one on the right. So that process would continue until we hit the second prime number, 1,013. And now the computation is about to wrap up. So the stream range function would expand as usual. We make it a shorthand. Call it C3, for this expression. So we, we have C3 filter is prime, apply zero. We apply the filter function that would say, well, 1013 is a prime number so let's include it in the list. Have the tile expression here. Apply zero of that. We apply, evaulate the apply function and that would pull out the first element 1013. And that's the result of the computation. Poof. That was quite tedious to follow that far, but imagine how more tedious it would have been if we had to evaluate actually all the prime numbers between 1,000 and 10,000. Here you could convince yourself that indeed, we never look beyond 1,013, all the other prime numbers remain undiscovered and unevaluated in this