In this session, we're looking at an interesting consequence of lazy computation, namely, that we can compute with infinite sequences. You saw that the elements of a lazy list are computed only when they're needed to produce a result. That opens up the possibility to define infinite lists. For instance, here's the lazy list of all integers starting from a given number, from n int. It's a lazy list of Int and it's defined to be n followed by from n plus 1. That's the list of all natural numbers. Now nats equals from zero. To compute the list of all multiples of four, we can do nats map times 4. Why does this all work? Well, it works because when we create the lazy cons here, the expression here will actually not be evaluated. It will only be evaluated when we need the head of a tale of the resulting lazy list. We can, in complete confidence, compute all natural numbers because they will in fact be computed only once somebody asks for a particular natural number. Of course, you can never ask for all of them at the same time, it's always a particular one and then you compute the natural numbers up to that number. The same holds for the multiples of four. I'll show you that in a worksheet. Here's the definition of from that you saw from the slides, let's define nats. That gives you a lazy list of int whose value is the lazy list that's not computed, so that's how it sprints. Now if you would write nats, let's say take 10. Let's take the first 10 element of the natural numbers and we still get lazy list not computed. When do we see anything in the lazy list? Well, one good way is to convert it to lists. We can do nats.take(10).toList. Now we see something. What happens if we just write nats.toList? I save it and we get a variant worksheets so nothing happens, of course, because to convert all natural numbers to a list that would never finish, the list would be infinite. So I stopped that computation. You might say that's cute, but is it useful? Yes, actually. You can do quite a few very useful things with infinite lists and other infinite data structures. One useful application is an ancient technique to compute prime numbers called the Sieve of Eratosthenes. The idea can be visualized as follows. We have an array of all numbers here, and we start with two. We say two is a prime number, so that's it. Now we cross out all multiples of two. Now the next number that's free is three, so three is a prime number and we cross out all multiples of three. That one is already crossed out, that one is freshly crossed out, that one is already crossed out, that one is freshly crossed out and so on. Now we see the next empty field, that's five. Five is a prime number and we cross out all multiples of 5. At each step, we take the next free field, that field is a prime number and then we cross out all its multiples. In the traditional formulation of the Sieve of Eratosthenes, the array here was finite, so you computed prime numbers in a certain interval from two up to the last field of that array. What we want to do now is we want to generalize the technique to compute arbitrarily many prime numbers without having an a-priori upper bound. To review the algorithm is we want to start with all integers from two, which is the first prime numbers. We eliminate all multiples of two. The first element of the resulting list is three, we eliminate all multiples of three and like that, we iterate forever. The list we work which needs to be a lazy list because it doesn't have an upper bound. Here's the C function that implements this principle, it's surprisingly short. Here we have def sieve, it takes a given list of integers and it filters them so that it takes the head of that list. Then all of the remaining elements we take, we keep only those that do not have the head as a divisor so that we keep only those elements x, where x modulo S dot head is different from zero. That remainder of the list gets recursively sieved again. The prime numbers now would be the sieve applied to all the natural numbers from two. That's testers in the worksheet. I've copied the definition of sieve and primes and now let's just find out what are the first 100 primes. Here we see the list or we see it also if you hover over it. Yes, that looks correct. Those are the first 100 primes. The lesson to take home here is that many algorithms that previously required an upper bound can now be extended to arbitrary size by using lazy data structures such as lazy lists. Lazy lists are also useful to define components that can be put together in very flexible ways. For instance, if we come back to our previous algorithm for square roots, there we always use the isGoodEnough test to tell when to terminate the iteration. We had in one loop the iteration, so compute the next step, and to test when it was good enough and we could return the result. With lazy lists, we can separate these two concerns. We can now express the concept of a converging sequence without having to worry about when to terminate it. Here we could have a square root sequence that essentially instead of computing the final value of square root, gives us the sequence of the approximations. Each improved step is what we had before. It's the average of the existing guess and x divided by guess. Then we have a lazy list of guesses that starts with one and is followed by guesses.map(improve). Now you might be puzzled and ask, "Well, that looks weird. Guesses uses itself, its own value in its right-hand side, but yet it is a val and not a def. How can that work?'' Well, it can work in some situations if the value of the val def is a lazy list or other lazy data structure. Let's trace the construction of the first few values of the list that we do here. The first value is clearly one. It's followed by the guesses.map(improve). The first value of what follows is the first value of the original list map with improve, so it's i of 1, where I abbreviate improve to i to save some space. What's the next value of the list? Well, the next value of the list is the second value of guesses.mapped with improve. It would be i of i of 1. The 4th value would be the 3rd value with improve. You see every value contains one further application of improve. What you have here is essentially the iterate of improve where every value applies improve one more time. That's precisely the converging sequence that gives you a square root. All you need to do in the end is return that guesses. You might still not be quite satisfied and ask, ''Well, why is this a lazy val? Why can't it be a normal val if you have argued so convincingly that the resulting sequence is okay.'' As always, let's try it out. We have here our original square root sequence. Let's remove the lazy val. What do we get? Well, we get a type error. It says, guesses is a forward reference extending over the definition of guesses. What Scala has, essentially, it has built-in checks that verify that we cannot have a recursive or mutually recursive or forward definition of a vowel in an local list. That check here refuses the program, but the check is conservative, so in principle, if the compiler would let this program pass, then the execution would still give us the same result. In principle it would be okay, but the compiler is overly conservative. However, we can avoid the check if we declare the value a lazy val. That is the solution of the mystery, why we need the lazy here. The point of having a sequence of approximations is that we can add the criterion when to terminate the sequence later. We could have now a separate method isGoodEnough that takes the current guess and the original value from which we want to take the square root and it just says, well, if the square of the guess minus x divided by x absolute is less than 0.001, then that's good enough. Then we can compose square root steps with GoodEnough. What this expression would do, it would keep only those approximations that are close enough to the value that we want. Let me show you that in the worksheet. We have the GoodEnough test here, and here we have the expression that filters the original square root sequent with the isGoodEnough test assuming that the value we want to take the square root of is two. If we save that, we get a lazy list, it's not computed. To actually get the final value of square root, we just take the head of that list. We have a series of approximation. We take those that are good enough and we take the first one that is good enough, that's the value of our square root algorithm. This is a very nice decomposition of the original square root problem into smaller and smaller steps. It was used as the original argument of quite an influential paper, which is called, Why Functional Programming Matters. The author of that paper was John Hughes. Let's finish with an exercise. Consider two ways to express the infinite list of multiples of a given number N. We could say the multiples of N is, take the list of all natural numbers and map each by times N, or we could say, take a list of all natural numbers and filter those numbers that modulo N are zero. Which of the two lazy lists which both compute the same result, generates the results faster? The 1st, the 2nd, or is there no difference? The answer is the 1st, simply because the 1st doesn't compute any elements that then are later dropped. For the 1st, every element we compute in the list will contribute to the result immediately with a map, whereas the second list generates more elements that then are discarded by the filter, so the first technique is faster.