0:00
Last session we have introduced high order functions as building blocks of programs.
High order functions are essential in many functional programming languages.
So much so that there's a special form to write them.
Which we are going to cover in this session.
So the topic of this session is currying, and I do not mean the food with that.
You'll find out in a second what that is. For our motivation, let's look again at
our summation functions, sumInts, sumCubes, sumFactorials.
We could factor out the body of these functions and now simply pass the function
to apply to each element in the interval. And the two pounds in the intervals.
But there's still repetition. Each of the three functions takes two
parameters A and B and then passes them on to the sum functions.
Can we get rid of these parameters and thereby become even shorter?
1:01
To see how that could be done, let's rewrite the sum functions as you see here.
So that sum function now takes only a single parameter, F, of type n to n as
before, and it returns a function. As its result indicated by this function
type here. So how can it return a function as a
result? Well, it defines a function in this body,
sum F, which takes the two bounds and does the usual computation that you've seen
before. And that function sum F will be returned
from sum. So sum is now a function that returns
another function. In particular, a locally defined function.
1:48
So if you do that, then we can in fact define sumInts to be simply sum of x,
arrow x, sum cubed is sum of the cubing function, and SUM factorial is sum of the
fact function. It turns out that these functions, they
are just exactly the same as the functions we defined before, and they can applied
just like the functions we defined before. So, we could say, SUM cubes one, ten, plus
SUM factorials ten, twenty. Next question.
In the previous example can we avoid the middleman, sum N, sum cubes, and the like.
Can we use directly the general sum function?
Of course. We simply write, sum of, in this case
cube, and then the bounds one to ten. Let's analyze that a, a little bit more.
So sum of cube applies to sum function. To the cube function.
And returns the sum of cube's function. Sum of cube is therefore equivalent to sum
cubes, and that function is next applied to the arguments one and ten.
3:10
This expression returns a function and that function then is applied to one and
ten. So here's another piece of syntactic
sugar. The definition of functions that return
functions is so useful in functional programming that we have a syntax for it
in skylar. So the A previous definition of sum
actually can be written shorter like that. We can just combine the, two parameter
lists of the outer function and the nested function and write them one after the
other. It would say sum of F int and then comes
another parameter list with A and B. And the body of the sum function is very
much like it was before. You might ask, well what's the point of
doing it that way instead of simply writing the function with three parameters
like we did at the beginning? Well the idea is that if you do that then
actually you can write sum of, let's say cube, separately.
That's a valid expression by itself and you can avoid passing the other arguments.
They will arrive in a different parameters list that can be applied later on in a
different context. So I've said that functions with multiple
parameter lists are syntactic sugar. Let's see how they expand.
If you have a function with multiple parameter lists like that, RX1, RXN equals
E, where N is greater than zero, that's, in fact, equivalent to taking the first N
minus one number of parameter lists. And for the last one, you create a fresh
function called G, that takes the last list.
Maps it to the body E, and G is the function that gets returned from F.
That was exactly the definition we did with sum F when we modeled the sum
function that dropped its second parameter list by having the inner function, sum F.
Or for short, we can just write an anonymous function.
So the, the, the definition up here would also be equivalent to the definition that
you see down here, where the last parameter list is, the parameter list of
anonymous function that maps to E. Now, if you repeat that process n times,
then our original n parameter list function is mapped to a function that has
no parameter list at all, but on the right hand side we have n nested anonymous
functions. This style of definition in function
application where essentially every function is mapped to an expression that
consists of anonymous functions, nested anonymous functions, that each take one
parameter, is called currying. It's named after its promoter, Haskell
Brooks Curry, who was a logician in the twentieth century.
It's no coincidence that Haskell Brooks Curry shares his first name with the
Haskell programming language. In fact, the, this idea goes back even
further to Shawn Finkel and probably Fricke, but the term Currying has stuck.
6:31
So let's take one more look at types. Given this function, sum f int and with it
two arguments, what is its type? The answer would be, it first, it is the
function that takes the function's argument, so that would be the argument
pipe. And that returns a function, that takes
two integers as arguments that would both be those two, and that returns finally an
int. So note that functional pipes associate to
the right. Here I could have written also.
Type like this. So, we take the intuitive function and we
return this function, but this blue parenthesis here are actually redundant.
Because, in general, at types such as int, arrow int, arrow int is read as int arrow,
and then the second pairs of ins are in parenthesis.
So we put the parenthesis for function types on the right.
7:50
As a second exercise, once you have product can you write factorial in terms
of it? And the third part of the exercise would
be, can you think of a more general function which generalizes both sum and
product? So let's see how we would answer that
exercise. I have already opened a new worksheet and
given you the product function and the parameters and results it takes.
So that's analogous to some product would take a function that goes from end to end
into two pounds, which are in ints, and we would return an int.
How would we define it? So we would go analogous to sum function.
First ask, well, if the bounds are empty, then we want to return the unit value for
multiplication. So for sum, the unit value was a zero, but
for multiplication, it's a one. So we return a one here.
And otherwise, we take f of a, and now times product of f and a, plus one, and b.
9:05
Let's test it. Oh, forgot the function.
Let's take the squares of all. Ints between three and seven.
That gives us a value which is hard to check, so let's do a smaller, 144.
That looks correct. Let's come to the second part of the
exercise. Second part of the exercise was, can we
define factorial in terms of product? So let's see how we would do that.
9:47
You have the factorial, which takes an int.
And what would that be? So it would be, the definition would be,
it's a multiplication of all numbers between one and m.
So it's a product where we pass as the function parameter the identity, and the
balance would be one and N. Again let's test that.
Factorial of five, would be 120, which is also correct.
The third part of the exercise was, can we find a function that generalizes sum and
product? Using that function we should be able to
express both sum and product. What could that be?
Well that function would then be parametrized in the type of mapping that
we want to apply, because that was something that was common in some end
product, plus the two bounds, plus the two things that differ.
The two things that differ were, what was the unit value and what was the combining
function with which we combined individual values of f?
In fact that what, what we are after is a version of map reduce.
We want to have a function, the function f would map values in the interval to new
values, and reduce, then would combine them.
So let's call this new function map reduce.
And what should we get as parameters? Well, it needs to get the f parameter and
it needs to get a new parameter called det combine that takes two ints and returns an
int. And finally, it needs to have a unit
value. We'll call it a zero value, which is an
int. So that's the value to return if the
interval is empty. And after that, we would have the same
thing as before. So we would have the two values of A and
B. How would we define it?
Well, let's take the definition of product as a template.
So if A is greater than B, then what do we return?
The zero value. Otherwise, we take F of A, and what we do,
we do with F of A, we're neither summing nor multiplying, but we now combine it
with the map reduce value of the same three parameters that we had initially.
And a + one and b. And it seems I'm missing, apparently.
12:37
Good. So that was the map reduce example.
Let's test it. Well a good way to test it actually would
be to say well let's define product in terms of map reduce, because after all, we
wanted a function that generalizes both sum and product.
So product then would be map reduce. Off.
What's the function, the given function? F is the mapping function.
The combining function would be a function that takes x and y and returns the product
of x and y. And the zero would be one for a product.
And we pass the parameters like this. And we delete the previous version of
product. Oops.
I had, had a problem here, where, let's see what it says.
In fact what happened here, we get two errors, which says we have a forward
reference, that extends over two definitions of values, reds zero and reds
one. If you remembered off the worksheet so
far, reds zero and reds one, where the values that named these individual
parameters. What happens here is that if you define a
function then in fact that function can refer to other functions but only as long
as there, that there are no definitions or expressions between two, the two
functions. So what we need to do then is move the map
reviews up in front of the product. So that we wouldn't have a forward
reference anymore. So now the program compiles.
And we would get the same value of factorial of 520.
So that means that we have substituted a correct definition, or seemingly correct
definition, for product. You've seen currying as a way to define
functions piecewise, one parameter section after the other.
In the next session we are going to take what we've learned with high order
functions, in another concrete example.