So far, All operations on lists were first order. That means, the functions took lists of primitive types as arguments and returned them as results. In this session, we're going to change that. We're going to introduce higher order list functions that work on lists and take another function as argument. We will see that with just a handful of these higher order functions, we can describe a great variety of different tasks. The examples in the previous sessions have shown that functions of lists often have very similar structure. In fact, we can identify several recurring patterns, such as transforming each element in a list in a certain way, Or retrieving from a list all elements that satisfy a given criterion, or maybe combining the elements of a list using an operator. And since we are in a functional language which allows programmers to write generic functions using high auto-functions, we can apply the same techniques to functions over the lists. So in this session, we are going to be interested in higher order functions over the lists. Our first common operation is to transform each element of a list, and then return the list of results. So for instant to multiply each element of a list by the same factor, you could write a function scale list which takes a list of doubles as input and a factor and returns a list of double and what it does is, well if the input list is nil, it just returns it unchanged, and otherwise it multiplies the first element of the list by the factor, and it does a recursive call of scale list with the rest of the list and factor. So, obviously, that function would multiply each element of the list by the same factor. That scheme can be generalized to a method map on the list class which can apply in arbitrary operation to all elements of a list. So here's a simple way to define map on the abstract class list of t. We would say def map and map takes a function from t to some. Other type, u, which could be the same as type t, or it could be different. So, u is a type parameter of MAP. And then it returns a list of u. And then the body of MAP is just the body of scale lists, but now generalized. So, in the case of nil, we return the list unchanged. If the list is non-nil, then we apply the function, F to the first element X, And we follow that with a recursive call of xs.mapf. In fact the actual implementation of map N class list and this kind of standard library is a bit more complicated for several reasons. First, the actual definition is in fact terra cursive. Where as this definition isn't you see after the call to map you still have a call to cons. The second the actual implementation to maps for arbituary collections not just lists. But for understanding map list definition here we'll do very well. So using map, we can now write scale list much more concisely, so much more concisely that, that it's hardly worth writing a different function for it. We would just say, scale list of XS and a factor S, map XS map, with the function that takes an X and multiplies X by the factor. So here's an exercise for you. Let's take a function square list that squares each element of a list, then returns the result. There are two possible ways to do that, either with pattern matching or using map. I invite you to try both possible ways by filling in the three triple question marks in the definition of square lists here and the definition of square lists down there. So let's see how we would do that. In the paren. Matching definition to take the squares of an empty list we would surely return the empty list again. To take the squares of a list with a head y and a tail ys, what do we do? Well, we start by taking the square of y and we follow that by a recursive call of square list. Of Y-S. So far so good. I think by now we all know how to do these things cold. But, let's see whether we can do it shorter using map. Well, to use square list with map, what can we do? Well, we map it by the function that takes an X, and returns X times X. And that's it. So obviously the definition with map is much shorter and I would, argue also clearer than the one that uses paramatching and recursion. So here's another common operation on lists, selecting all elements that satisfy a given condition. For instance, you might want to select all elements from a list that are positives. Here you have a function pause alps. It takes a list of int, gives you back a list of int. And the para matching definition would read as you see here. So for the empty list we can just return it. If its non-empty and the first element is in fact greater than zero it's positive, then we include it in the result list. So we return the first element followed by. Pause LM's of the rest of the list. And, otherwise, we just do pause LM's of the rest of the list. So, the first element gets dropped, and we just filter the remainder of the list with pause LM's. Again, this pattern can be generalized to the method filter in the list class. So here you see the definition of filter. It takes now a predicate that takes an element of the list element type t, that gives you back a boolean, and it will return a list of t's. The definition is an obvious generalization of posElems that we've seen before. So we match on the current list. If it's nil then we return to middle list. And if it's not nil and the head of the list XX satisfies our predicate So P of X is true. Then we return X followed by X dot filter P. And otherwise just accept that filter P. Again using filter we can write poseElems much more concisely. We will just write excess filter x such that x is greater than zero. Besides filter, there are also other methods that extract sub-lists a list based on some predicate. You see the list of these methods here. Rather than going through the list, I just wanted to show them in action in a new worksheet. So what I'm going to do is I'm going to take the data definitions of the previous worksheet and create a new worksheet. Call this, call this list far. . And we have our test data here. And what I'm, what we're gonna do is lets say the first one would be numbs filter so all numbers greater than zero, so that would have filtered out the -four here. If we do, nums filter not X, X greater than zero, what do you expect to get? Right. You'd expect to get just the negative number, -four. The other. And as that was partitioned. Partition is like filter and filter not in one go. So, if you write that here. Then what you see is you get a pair of two lists. The first list contains all those elements for which the predicate is true. So that was the positive elements. And the second list contains all those elements for which the predicates is false. So you see the partition is just like filter and filter not as a pair. However it will run in a single traversal through the input list numbers, where as if you do first a filter and a filter not you would get two traversals. The next two functions are a bit different in that they look at a prefix and a suffix of a list. So what I can do here is I can say, Nums TakeWhile. X, X greater than zero. So what that gives me is the longest prefix of the list, such that the predicate is true. So here I would say okay two is greater than zero. But then at minus four, I would stop, because minus four is not greater than zero. So it will, any further elements will not take part in take while. That's the main difference between take while and filter. Filter will always. Select all elements in the list that satisfy the criteria, whereas take while will only take the longest prefix of the list. The opposite of take while is drop while. So, let's write that. So take while and drop while relate to each other such, just like take and drop relate to each other. Drop while would then return the remainder of the list, without the prefix taken by take while, so it would be the list that starts with a negative element and then goes until the end of the input list. And finally, where we had partition, that combined a filter and filter not, we also have an operation that combines a take while and a drop while. That operation is called span. So if we do that then what we will see is that it will give us essentially the combination of a TakeWhile, that was a list two and a DropWhile. But like Partition, it will only need a single traversal, not two. Let's apply the function that we've seen so far in an exercise. The task is to write a function pack that packs consecutive duplicates of list elements into sublists. So if you apply pack to this input list here, we would expect to get back a list of lists, where the first sublist is formed from, from the three consecutive A's here. The second sublist has just a single B. The third sublist has the two consecutive C's. And the final sublist has the trailing A here. The idea is to use a template like this one here, we have a defined pack to be a generic function over type lists of T, returns a list of lists of T, Obviously if the list is empty, then that's what we would expect back. So the only case to handle is really this case here. If the list is nonempty, what do we do? I've already copied my input list data and the template of the pack function, so the only case to fill in is when the list is nonempty, consisting of a head, X, and a tail, XS. Which of the six functions here would be applicable? Well, what we wanna do is take off a leading sub list and then do something with the rest of the list. So it's a combination of take while and drop while and that's what span would give us. So let's set up a para match first rest equals xs span and what should be the predicate be. Well we say take elements as long as they are equal to x, the leading elements of the list. Once we have that we would say, first is already the sublist that will constitute the first element of our list, and the other elements would be the result of a recursive call of pack to the rest of the list. And that gives us our function, pack, so let's apply pack to our data list. Pack of data gives us. A list consisting of three As, one B, two Cs, and an A. Just what we needed. We're not done yet as a second exercise I would like you to use Peg to write a second function and code that produces a run length encoding of a list. Run length encodings are often used for compressions of images and other files. The idea is to encode n consecutive duplicates of an element of the list as a pair xn. So instead of writing the element n times we just have a single entry pair of what the element was and how many times we have written it. For instance. Is in code of the list that we've seen before should give us. A3, B1, C2 and again, A1. So, let's go again to the worksheet to solve that. What we're interested in is a function in code, and it should also be generic taking a list of T, and now it would return a list of pairs of the element and the count which is an integer. And what should the body of encode be? Well, it turns out that most of the work has already been done by pack. Once we have a packed list. All we need is, is a simple transformation to get to the run length encoding. And that transformation will be applied to each element. So the natural operation to use is a map. So what we do is we start with pack of excess. And we then apply our map. A pack of Xs lead to, here's a list of lists. So if we apply our map then we have get each individual sub list as an argument. Let's call that sub list Ys here and what do we do in the map, well, we return a pair where the first element of the pair is the first element in the sub list and the second element of the pair is the length of that sub list. There we are with encode. All we need to do is encode of data. To get a single test case. And we get what we expected, a 3B, one C2, and A1.