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.