0:00

In this session, we'll continue our study of high-order list functions.

We'll introduce a new class of such functions which are called fold or reduce

combinators. There's several variance of these

combinators, but what they have in common is that they insert and give an operator

between adjacent elements of a list. Another common operation on lists is to

combine the element of a list using a given operator.

For instance, to take the sum of a list, you would put a plus between adjacent

elements of the list or to take a product, you would use, multiplication operation

between, adjacent elements. To cater for empty lists, so if n equals

zero, we can, we, we deal with that by actually taking zero plus in the sum and,

one times in the product. So each time we take the unit value of the

operation as a first operation here on the left.

So that way the, our definitions of sum and product can also deal with empty

lists. And that, of course, can be implemented with the usual recursive

schema, So could define sum takes a list of Int,

and gives you a an Int. If the list is Nil, then we return the unit element zero,

Otherwise, we return y plus sum ys. Again, you might ask how can I generalize

that pattern, and in fact it can be abstracted using the generic method

reduceLeft. So reduceLeft inserts a given binary operator between adjacent elements

of a list. So, if you have a list of from x1 to xn

and we say reduceLeft up and we would put an operator between each successive

elements of the list. If we draw that as a tree, it would look

like this one here, So we have a list of x1,

2:02

X2 and so on until we have the last operation that's the last element of the

list. Once we have reduceLeft, we can express

sum and product with it. So sum just would be take the list that

just starts with the zero and then following the list xs and reduceLeft with

a plus operation. So it would look like as thing here, would

be zero, X1 plus, plus x2 and so on, With a plus xn.

And product would be the same thing except that at the lower left corner of this tree

here you would have a one, and as an operation you would have a times.

2:59

By the way, Instead of writing, functions like this

one here, two parameters xy then x times y, Scala actually lets you write that also

in a shorter way using underscores. So you could just write, underscore times

underscore for this very same function. The idea is that every underscore

represents a function parameter, so if you have several ones, then each one would

represent a new parameter going from left to right.

And the parameters then are implicitly defined at the next out of pair of

parentheses. So that's why here you would read this

expression as first saying, well, this defines two parameters, x times y.

And here are my parentheses, so that's where I insert the parameters x and y that

I have just synthesized and that gives us precisely the function that you see here.

So sum and product can, in fact, be expressed even shorter, like this.

Sum would be zero followed by xs, reduceLeft with a binary operation plus.

And product would reduceLeft with a, binary operation times.

4:06

So, by its very nature, reduceLeft can only be applied to non-empty lists.

There's a more general function which is called foldLeft, which can also applied to

empty lists. The idea is that forwardLeft takes an

operation and a so called accumulator or zero element zet as an additional

parameter and that zero element would be returned if the list is empty.

So the idea here would be that you would use foldLeft like this.

Here would be a list, then you would call foldLeft with the zero element and then

you would apply the operation as an additional argument. And that would then

expand to the following tree here, And let's say we have the zero element and

x1, and that would be combined with the operation, and then the other operation

would take x2 and so on until finally the last topmost operation, would combined the

result of all the previous elements with the last element as right upper.

5:16

So, sum and product can also be defined as follows, using foldLeft instead of

reduceLeft. For sum, we say fold with a plus and zero element is zero, and for

product, we fold with a times and the unit element is a one.

So here you see some possible implementations of foldLeft and reduceLeft

as methods in class list. So reduceLeft, let's look at the type

first. It would take an operation that takes two

operands of the list element type and returns a result of the type and the

result of reduceLeft is again, the list element type.

The list must be non-empty, So in the case of Nil, we throw an error,

Nil reduceLeft. And if the list is non-empty, so it comes

just of a head x and the tail xs, Then, what we do is we forward to the

6:10

foldLeft method with a zero element x. So that's the element that will be

returned when xs is empty and the operation we have pass to reduceLeft.

Now that leaves us with foldLeft. So type of foldLeft is a bit more

complicated. Let's ignore it for the moment.

We'll get back to it. But let's look at the body.

So if the list is empty, then foldLeft would return its zero element.

Zeta. If the list is non-empty, then what we do

is, we have another call to foldLeft with zero element that's now the operation

applied to the former zero and the first element of the list.

So lets draw this. So, in the first iteration, foldLeft would

be applied recursively, with the accumulator of the first element of the

list, which I call here x1, and the operation.

Now, the second call would then apply the operation to the zero that we pass into

the second call, this, this subtree here, and the second element of the list and so

on. our accumulator grows with each recursive call to foldLeft, until finally

we are, Have an accumulator that looks like that.

And there are no further elements in the list, so the list is empty and in that

case we would return the accumulator. So it's a classic loop with an accumulator

that implements foldLeft. So let's look at the type of foldLeft now.

So, we know that the list elements are all of type T,

So I can write T: for each one of those. The zero can be of a different type, u.

8:07

also have that the subtree has type u and so on.

All the subtrees have type u, up to the result of the foldLeft.

So that type annotated tree matches the types that you see here.

Type u is arbitrary, Zero has the type u.

The operation, then, must have the type that takes a u and a T to a u and the

result of the final foldLeft operation is a u.

Now we've seen that foldLeft and reduceLeft produce trees that lean to the

left. So it would make sense to have a dual pair

of operations that unfold to trees that lean to the right.

These are called foldRight and reduceRight.

Let's look at reduceRight first. So reduceRight puts an operator between

adjacent elements, but now the parentheses go to the right, not to the left.

So, visually represented as trees it would like, would look like this.

So we would have the first operation takes x1, and the whole result of producing a

9:15

foldRight of the rest of the list and at the end, I would xn minus one and xn here.

So we get a tree that leans to the right. Foldright is, analogous to foldLeft.

It takes, again, a zero element or an accumulator here,

So we would, what we would see here, it would be something similar.

It would be an operation that takes the first element.

Then the second element of the list if it exists, and so on, until it finally it

takes the final element of the list and combines it with the zero here.

So, if the list is empty, reduceRight would be undefined, just as reduceLeft is

not defined for empty lists. So if you look at the possible

implementations of reduceRight and foldRight and class lists, then here they

are. So for reduceRight, it takes again a

binary operation from T, T to T. Would say for empty list, it's again

undefined. If the list consist of a single element,

then that's the element that's going to be returned.

And otherwise, it's going to be the result of the operation applied to the first

element of the list and a recursive call of reduceRight to the rest of the list.

For foldRight, we have a small typo here, so it takes a type parameter which is a u

and a zero element of type u and then an operation and it's, implementation will

simply be for an empty list, we return zero element, as for foldLeft.

For a non-empty list, we return the operation that takes the headed element of

the list and the result of applying foldRight recursively to the tail of the

list. So to expand it out, if we had a non-empty

list, what we would get is, we would have the first element of the list x1 applied

with operation of recursively applying foldRight to the tail of the list.

So, let's say there's a second element x2, Again recursive call until finally we

would have the last element, Then we would hit an empty list and the

result of foldRight is set so you see the same right leaning tree that I've shown

you on the previous slide. Quickly look at types so the xn's again

all have type T's as before. The zeta is type u.

And then, it follows that the operations must also return the same type u, because

like the zeta, they are all used as a right-hand operand offer successive

operation. And then u is also the type that is

returned from foldRight. So foldLeft and foldRight produce

different trees, Left leaning trees and right leaning

trees, But maybe they produce the same result.

In fact, if our operator is associative and commutative,

One can show that foldLeft and foldRight are equivalent functionally,

Even though there might be a difference in efficiency.

But sometimes, only one of the two operators is appropriate.

12:38

So we see this in an exercise. Here is another formulation of the concat

function that you've seen before. So let's look at this in a little bit more

detail. So, what we mean here is that we have, do

a foldRight over a list xs with zero element ys and the operation is a cons.

So let's draw that out graphically. So, we would have x1 operation cons,

X2, operation cons, and so on, Until to, the end of the list operation

cons, And then we would have the list ys.

13:47

So what do we see here? Well, it's just a single list that

consists of element x1 to xn and then y1 to yn.

So that, that shows us that, the operation cons here, with the ys as zero element,

provides exactly the list that we need for the concatenation.

Now, that was okay for foldRight. Can you replace foldRight by foldLeft

here? In fact, you can but you should tell us

what is goes wrong. The types might not work out,

The resulting function might not terminate,

Or the result might not be what you want. For instance, it could be the reversed of

the result that, that you what to have get back from concat.

14:40

. Okay. so, to answer this question, let's

simply type the definition of concat to our worksheet and we place foldRight by

foldLeft and we get the error message, value:: is not a member of the type

parameter T. So, what happened here was well, we do a

foldLleft over a list xs, So we apply the operation to each element

of that list xs and that's a T and in fact the operation cons is not applicable to

arbitrary elements, it's only applicable to lists.

So that's why what the right answer is we would get a type error in this case.