next up previous
Next: About this document Up: My Home Page

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) =
= 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
Neither of the two definition is acceptable by itself.
- 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 list
such 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,

  • first split L into two disjoint sublists (of about equal size),
  • then (recursively) sort the sublists, and
  • finally merge the (now sorted) sublists.
This recursive method is known as Merge-Sort.

It evidently requires us to define suitable functions for

  • splitting a list into two sublists and
  • merging two sorted lists into one sorted list.

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) =
= if L=[] then []
= else merge(sort(take(L)),sort(skip(L)));
val sort = fn : int list -> int list
Don't run this function, though, as it doesn't quite work. Why?

To see where the problem is, observe what the result is of applying take to a one-element list.

- take[1];
val it = [1] : int list
Thus in this case, the first recursive call to sort will be applied to the same argument!

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.




next up previous
Next: About this document Up: My Home Page

Steve Skiena
Tue Aug 24 20:57:30 EDT 1999