Higher-Order Functions
In functional programming languages, variables may denote functions and be used in definitions of other, so-called higher-order, functions.
One example of a higher-order function is the function apply defined below, which applies its first argument (a function) to all elements in its second argument (a list of suitable type).
- fun apply(f,L) =
= if (L=[]) then []
= else f(hd(L))::(apply(f,tl(L)));
val apply = fn : (''a -> 'b) * ''a list -> 'b list
We may apply apply with any function as argument.
- fun square(x) = (x:int)*x;
val square = fn : int -> int
- apply(square,[2,3,4]);
val it = [4,9,16] : int list
The function doubleall we defined above may be considered a special case of supplying apply with first argument double (a function we defined in a previous lecture).
- apply(double,[1,3,5,7]);
val it = [2,6,10,14] : int list
Mutual Recursion
Sometimes the most convenient way of defining (two or more different) functions is in mutual dependence of each other.
Consider two functions, take and skip, both of which extract alternate elements from a given list, with the difference that take starts with the first element (and hence extracts all elements at odd-numbered positions), whereas skip skips the first element (and hence extracts all elements at even-numbered positions, if any).
SML uses the keyword and (not to be confused with the logical operator andalso) for such mutually recursive definitions.
- fun take(L) =Neither of the two definition is acceptable by itself.
= if L = nil then nil
= else hd(L)::skip(tl(L))
= and
= skip(L) =
= if L=nil then nil
= else take(tl(L));
val take = fn : ''a list -> ''a list
val skip = fn : ''a list -> ''a list
- take[1,2,3];
val it = [1,3] : int list
- skip[1,2,3];
val it = [2] : int list
Sorting
We next design a function for sorting a list of integers.
More precisely, we want to define an SML function,
sort : int list -> int listsuch that sort(L) is a sorted version (in non-descending order) of L.
Sorting is an important problem for which a large variety of different algorithms have been proposed.
The method we will explore is based on the following idea. To sort a list L,
It evidently requires us to define suitable functions for
Merging
First we consider the problem of merging two sorted lists.
A corresponding recursive definition can be easily defined by distinguishing between the different cases, as to whether one of the argument lists is empty or not.
The following SML definition is formulated in terms of patterns (against which specific arguments in applications of the function will be matched during evaluation).
- fun merge([],M) = M
= | merge(L,[]) = L
= | merge(x::xl,y::yl) =
= if (x:int)<y then x::merge(xl,y::yl)
= else y::merge(x::xl,yl);
val merge = fn : int list * int list -> int list
- merge([1,5,7,9],[2,3,5,5,10]);
val it = [1,2,3,5,5,5,7,9,10] : int list
- merge([],[1,2]);
val it = [1,2] : int list
- merge([1,2],[]);
val it = [1,2] : int list
How do we split a list? Recursion seems to be of little help for this task, but fortunately we have already defined suitable functions that solve the problem.
Merge Sort
Using take and skip to split a list, we obtain the following function for sorting.
- fun sort(L) =Don't run this function, though, as it doesn't quite work. Why?
= if L=[] then []
= else merge(sort(take(L)),sort(skip(L)));
val sort = fn : int list -> int list
To see where the problem is, observe what the result is of applying take to a one-element list.
- take[1];Thus in this case, the first recursive call to sort will be applied to the same argument!
val it = [1] : int list
Here is a modified version in which one-element lists are dealt with correctly.
- fun sort(L) =
= if L=[] then []
= else if tl(L)=[] then L
= else merge(sort(take(L)),sort(skip(L)));
val sort = fn : int list -> int list
Finally, some examples:
- sort[];
val it = [] : int list
- sort[1];
val it = [1] : int list
- sort[1,2];
val it = [1,2] : int list
- sort[2,1];
val it = [1,2] : int list
- sort[1,2,3,4,5,6,7,8,9];
val it = [1,2,3,4,5,6,7,8,9] : int list
- sort[9,8,7,6,5,4,3,2,1];
val it = [1,2,3,4,5,6,7,8,9] : int list
- sort[1,2,1,2,2,1,2,1,2,1];
val it = [1,1,1,1,1,2,2,2,2,2] : int list
Tracing Mergesort
It is important to be able to trace the execution of the mergesort program to convince yourself that it works correctly.
In the course of executing the recursive algorithm, the computer has to keep track of what work still needs to be done as it is interrupted with additional recursive calls.