In this session and the next we are dealing with laziness. Laziness means computing something as late as possible, only computing it once it's first needed. We're starting with Lazy Lists. For motivation we've seen a number of immutable collections that provide powerful operations, in particular for combinatorial search. For instance, to find the second prime number between 1,000 and 10,000 is really easy, you can write well between 1,000 and 10,000 filter with this prime predicate and take the second number so add index one. It's a lot more tedious to write this with a recursive function from scratch. Here you see a possible implementation, second prime is implemented in terms of nth prime, and then essentially nth prime has this tedious thing with counters that essentially counts the number of primes that were found and then if it found the last prime that's necessary, it will return into otherwise, it will go into the recursion one way or another. On the other hand this more convoluted implementation is a lot faster than the one we wrote up here. Indeed, from a standpoint of performance that expression is really bad, we've constructs all prime numbers between 1,000 and 10,000 in a list, but only ever looks at the first two elements of that list, the rest list are thrown away. Of course, we could reduce the upper bound, we know that the second prime number will be a lot closer to 1,000 than to 10,000, but of course if we do that too much then we risk missing the second prime number altogether, so we have to put some knowledge in from the outside, which we might not have. However, we can make the short code efficient by using a trick. The trick is that we want to avoid computing the elements of a sequence until they are needed for the evaluation result, which might be never. This idea is implemented in a new class and this Scala Standard Library, which is a lazy List. Lazy Lists are similar to lists but the elements are evaluated only on demand. Here's how you can work with Lazy Lists. They're defined from a constant lazy list dot empty, which is the analog of nil for strict lists, and a constructor lazy list dot cons, which is the analog of colon, colon for strict lists. For instance, lazyList dot cons, 1, Lazylist dot cons, 2, LazyList dot empty is the LazyList one followed by two, followed by empty. In what sense is it lazy? Well, it's lazy in the sense that a sub-expression like this one here will be evaluated only when we need the second element of the LazyList. LazyLists can be defined just as the other collections by using the object LazyList as a factory. You can write LazyList 1, 2, 3, that translates to LazyList dot apply 1, 2, 3 and indeed the LazyList object has a suitable apply method that will convert these arguments to LazyLists. Another way to get LazyList is by converting some other collections to LazyLists, you can do that with the two method which is available on most collections in the Scala Library. You write 1-1000 dot to and then LazyList, and that will turn this range 1-1000 into a LazyList. To, is another utility method defined on Scala collections, it essentially takes a object which is a factory and cause the apply method on that object on the elements that are to the left of the to. We can use some of these elements to define further methods that construct LazyLists. For instance, LazyRange here. LazyRange will construct a LazyList off the numbers between lo up to high, it's a Lazylist of int and it's simply defined as you see here. If lo is greater or equal high then it's the empty LazyList otherwise it's the cons LazyList with the first element being low and the rest being the result of LazyRange of lo plus ho and hi. If you compare to the same function that produces a list, which you see here and you see that the two functions are very, very similar the only differences are that we use LazyList are empty instead of nil and LazyLists of cons instead of colon, colon. Otherwise is exactly the same. The two functions have almost identical structure yet they evaluate quite differently. ListRange start, end will produce a list with end minus start elements and return it immediately. LazyRange start, end will return a single object of type LazyList. The elements are only computed when they are needed, when needed means that somebody calls head or tail on the LazyList or is empty, I should say. Until that time the LazyList object is not evaluated, we don't know yet what's in it. LazyLists supports almost all methods of List, including filter. For instance to find the second prime number between 1,000 and 10,000, we can now write LazyList of range 1,000,10,000 filter is prime element at index one. The one major exception where the operations are different is cons, so x cons as always produces a List, never a LazyList. There is however, an alternative operator, hash cons, which produces a LazyList, so x hash cons xs is the same thing as LazyList dot com x, xs. What it does is it produces a lazy list consisting of x as the first element, xs, at the rest, and it does so lazily, that means the whole thing will be evaluated only when somebody needs to access an element of that list. Like cons, hash cons can be used in expressions as well as in patterns. This idea of laziness looks a bit magic at the beginning, and indeed the actual implementation of lazy lists is quite subtle. As a simplification to approach the subject, we consider for now that lazy lists are only lazy in their tail. That means the tail of a lazy list is computed when the first time somebody needs it, but the head and it's empty predicate of a lazy list are already set when the lazy list is created. That's not the actual behavior of lazy lists, but it makes the implementation simpler to understand. Let's call this simplified lazy lists TailLazyList because it's only lazy in the tail. Here's the basic trait, trait TailLazyList should be a sub-type of sequence. It has it's empty method and the head and tail method. The same fundamental operations as a normal list. As for normal list, all other methods can be defined in terms of these three fundamental operations. Now to finish the implementation, we just have to give concrete implementations of empty and non-empty lazy lists that define these three methods. Here is the empty lazy lists. It's a TailLazyList of nothing, isEmpty is true, head and tail throw an exception, and there's also a toString method so that we can print these things nicely, which just says it's lazy list empty. What about cons? The main difference between cons and the list cons we have seen is this. The tail function now is by-name parameter. It takes a head, which is a normal by-value parameter and the tail, which is a by-name parameter of type lazy list of t. It returns a new lazy list of t, where is empty is false, the head is the head, the tail is the tail argument here, and toString is lazy list head and then comma, and then we just put a question mark in here in order not to force the lazy lists. Converting the lazy list to a string, we don't really want to find out what the elements are by forcing the elements. We just print what we know. In this case, because the head is a by-value parameter, we know what the head is, but we don't know by the table what the tail is without calling tail. When we call tail, then it would call this by-name parameter, and that would in effect evaluate the expression that computes the tail of that lazy list. The only difference between lists and lazy lists is really this, is just the arrow in front of tail. To reiterate for lazy lists, tail is a by-name parameter for normal lists, it's a by-value parameter, and that's why the second argument of TailLazyList.cons is not evaluated at the point of call. Instead, it will be evaluated each time someone calls tail on a lazy list object. You could argue, well that's actually pretty bad because if tail could be called multiple times, which would lead to multiple evaluations of this by-name parameter, then that's the correct objection and we will address that in the next session. Once we have defined lazy lists or TailLazyLists like that, we can define other lazy list methods just like the methods for this. For instance, here's the implementation of filter on a TailLazyList, it's really exactly the same as we would define filter on a normal list if you wanted to base it on the three fundamental operations isEmpty, head, and tail. As an exercise for you, here's a slight instrumentation of lazy range. It's what we had before, but each time we go into the body of lazy range, we print the current value of lo. When you write lazy range 110, take 3 tolist, what gets printed? Nothing,1, 1,2,3, 1,2,3,4, or 1,2, up to nine. Let's see for a solution. If I just write lazy range 110, what would be printed? Well, I would expand it to the right-hand side which would print 1, and then depending on the two branches, would in each case return a lazy list, which means that nothing further needs to be printed because that expression is not executed. Lazy range 110 just prints 1. What is lazy range 110 take 3? Well, we don't have an implementation of take 3 here, but essentially you can imagine that it takes the head of the list, it takes the head of the tail of the list, and it takes the head of the tail of the tail of the list, that's the three elements it takes. If it takes the head of the list, nothing further is printed because the head is already evaluated, so we already have the 1. If it takes the head of the tail of the list, now we go into tail once. We go into this recursive instance again, so we print the second element, 2. If we take the head of the tail of the tail of the list, we print the third element, 3, and that's it. That's all we need for take 3. If we convert it to a list then, of course, we get the list 1,2,3, but before we get that, we print the elements 1,2,3, and not more than them.