0:00

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.

4:42

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.

7:13

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.

9:29

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.

12:27

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.

13:43

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.