In this session, we'll introduce higher-order list functions. You've seen already that higher-order functions can be very useful. That holds particularly for lists where there are many very common operations that can be abbreviated with higher-order functions. The list examples we've seen have already shown that functions on lists often have quite similar structures. We can identify several recurring patterns like transforming each element in a list in a certain way, or retrieving a list of all elements that satisfy some criterion, or combining the elements of a list using some operator. Function languages allow programmers to write generic functions that implement patterns such as these, using higher-order functions. Here's an example of the first category. A common operation is to transform each element of a list and then return the list of results. For instance, we might want to multiply each element of a list by a given factor. We could do this with this function here, scaleList. It takes a list and the factor, and since, well, if the list is Nil, then return it. If it's not Nil starting with y, then multiply y with factor and do a recursive call of scaleList of the rest with the same factor. This game can be generalized to the method map of the list class. A simpler way to define map is shown here. We've defined it as an extension method on a list of arbitrary type T, mapped x and a function from T to some new typed U as additional parameter, and it returns a list of use. It's implemented as follows; we do a pattern match on xs. If the list is Nil, then we return the empty list. If the list is not Nil with head element x, then we apply the function f to x and concept with the recursive call on map over the rest of the list with the same function f. In fact, the actual definition of map is a bit more complicated because it is tail-recursive, and also because it works for arbitrary collections, not just lists. But to understand the functioning on a map for lists, this will do. Using map, we can now write scaleList more concisely. We can just say scaleList is xs.map of x taken to x times factor. Let's do a similar exercise. Let's consider a function to square each element of a list and return the result. There are two equivalent ways to do it, one's as a recursive definition and the other as a call to map. Let's fill in the question marks for both of the alternatives. The recursive definition, we map Nil to Nil, we map y followed by ys to y times y con squareList ys as usual. For map, it's a lot simpler, squareList is simply xs.map with the squaring function. We see that map is a very handy tool for all cases, where we transform each element of a list, given some transforming function. Another common operation on Lists is to select all elements that satisfy a given condition. For example, here we have a function that takes the positive elements of a list of integers. PosElems is defined with the usual recursive pattern match. If the list is Nil, we return access. If the list is not Nil with head element y and y is greater than 0, then we return a new list with y, followed by posElems ys, and otherwise, we just return posElems ys without the head 1, element y. This pattern is generalized by the method filter of the list class. Here's a possible definition of filter as an extension method. It's defined on lists of arbitrary type T. It takes a predicate function that maps values of T to type Boolean and returns a list of T. Its implementation is a pattern match on the list xs. If xs is Nil, we return xs unchanged. If xs is not Nil, then if the predicate holds for the first element of the list, then that gets included in the result, and we follow with the recursive call filter p on the other elements xs, and otherwise, we just return filter p over the other elements xs without the head element x. Using filter, we can now write posElems more concisely like this. It's just xs filter with the function x that tests whether x is greater than 0. Besides filter, there are also the following variations that extract sub-lists based on a predicate. We have filterNot, so that's the same as filter with a predicate that negates p. It's a list consisting of those elements of xs that do not satisfy the predicate p. Then we have partition. That's the same as the pair of lists. The first list is xs filter p, the second list is xs filterNot p. The first list gives you all the elements of xs that satisfy p. The second list gives you all the elements in xs that do not satisfy p. But the two lists are computed in a single traversal of the list xs, so it's more efficient than two separate calls to filter and filterNot. The takeWhile method is a bit like filter, but not quite. It gives you the longest prefix of the list xs consisting of elements that satisfy the predicate p. The first element that does not satisfy p terminates the output, whereas filter would go on and look for other elements that satisfy p in the rest of the list. Dropwhile is the dual of takeWhile. So that means we drop elements of the list until there's an element that does not satisfy p. We drop the longest prefix of the list of elements that do satisfy p, and finally, we have xs.span p. That's the same as takeWhile dropWhile, but computed in a single traversal of the list xs. To illustrate the difference between partition and span, let's write a list of numbers, 1-6 and partition the list with the predicate, testing whether an element is odd. What we get is two lists. The first list are the list of the odd elements, 1, 3, 5, and the second list is a list of even elements. Let's do the same thing now, but with span instead of partition. Now we get a list, one as the first element. That's the longest prefix of the list that consists of odd elements, that's just the single element. The remaining list is all the elements after this first element so 2, 3, 4, 5. So here's an exercise for you. Write a function pack that packs consecutive duplicates of list elements into sublists. For instance, if you have a list consisting of three as, a b, two cs and then again an a. Then we should return a list consisting of four sublists. First the three as, then the single b, then the two cs, and finally the remaining a. You can use the following template. Pack takes a list of arbitrary type, T, returns a list of T, and it uses the usual pattern match between nil and non-empty lists. I've set up the example in the worksheet. We have the template for the pack function. We have the example list elems and call pack elems right now gives us a not implemented error because of the triple question marks here, which we have to replace. So what do we do in the case of nil? Well, if the list is empty, then we should return the empty list because there's nothing to pack. What if x is not nil say it starts with x followed by xs. One thing we should do is we should collect all elements that are equal to x, and and they will be collected into the more rest and then that should be followed by all elements that remain in the list. We can do that with the call to span and to predicate that tests whether a given value is equal to x. Once we have that, what do we do? Well, we could return more followed by pack of rest. Let's see whether this would work. We get a result, but there's always the first value missing. We get only two as instead of three, no b instead of one, one c instead of two. Where did we go wrong? While we haven't included the x in the result list it's just the following elements that equal x that we excluded, but we forgot x. So Let's add x to the first element as the first element of the first element of the result list and that would now give us a result which is correct and the logic looks good as well. Now let's follow this up with another exercise. Using the pack function that you've written before, write a function encode that produces the run-length encoding of a list. The idea of a run-length encoding is that we want to encode consecutive duplicates of an element x as a pair x, n. For instance, the encoding of this list here should give us a list of four pairs. The first pair says we have three as, the second pair says we have one, b, two cs, and finally one a. A run-length encoding of a list can compress a list if it is quite common that an element appears several times consecutively in the list. Here's the outline of encode, it takes a list of t and returns a list of pairs of t's and int. that the length-counts and all that remains is implementing it. I've given the template for encoding the worksheet. To implement it let's start with packing elements. That would give us a list of list of T's. Now what we have to do is get from this list of T and the element list to a pair which is the element in the list plus the length and we do that with a map. We say a map with the function x that returns x.head as the element and x.length as a list and if you do that, then we get a list of pairs of strings of integers, which consists indeed of three as, one b, two cs and one a.