------------------------

Harvey Mudd College
Computer Science 131
Programming Languages
Spring Semester 2000

Lecture 04

------------------------

------------------------

What is a higher-order function?

It is just a function that takes or returns another function.

A first-order function is one which operates on individuals (anything that is not a function). A second-order function operates on first-order functions, and so on. A higher-order function means anything that is not first-order, or, more generally, a function that will operate on functions of any order.

Curried functions are higher-order in the sense that they return functions as a result of their partial application. Here are a couple of examples of functions that take functions as parameters.

	fun double f a = f (f a);
	
	fun quadruple f a = (f (f (f (f a))));

 

 

------------------------

The first three H.O. functions I want to talk about to manipulate lists, the most popular data structure in functional programming. The functions are:

------------------------

Map takes a function (of type 'a -> 'b) and a list of items (of type 'a) and returns the list (of elements of type 'b) that results from replacing each element in the argument list by the result of applying the argument function to that element.

Now, map is built in, but it is simple and instructive to write ourselves.


	fun map 
	
	  | map 

So, suppose we want to add one to every element of a list, we could use map as follows:

	map                       [3,7,9];
If we want a function that will add one to every element of a given list we just write:
	fun incList l = 
or, more succinctly, using partial application:
	val incList = 

------------------------

The function filter takes a predicate (a function from 'a to bool) and a list of elements of type 'a and returns the list of those elements for which the predicate returned true. Filter is written as:


	fun filter 
	
	  | filter 
	

So, if we want to remove all the 0s from a list we would write:

	filter                                     [2,0,3,4,0,5,6,7,0,9];
and, as above, we could store this capability off in a function as in:
	val removeZeroes = 
 

 

 

The remove_vowels function from the first assignment can be coded as a call to filter. Think about how you would do it.

------------------------

The last of these functions, fold is the most complicated. In fold the idea is to successively combine all the elements of the list together to get a single answer. For instance, you could use fold to add up the elements of a list. Let's look at how you would use it, then we should be able to figure out how you'd write it.

	fun sumList l = fold (op +) 0 l;
So, the first parameter is what you want to do to the successive values of the list; the second parameter is the value you want to use to get the ball rolling, and the third value is the list. To write it we say:

	fun fold 
	
	  | fold 

It is instructive to reason out its type:

To use it to multiply the elements of a list, you would say:

	val multList = 
As it turns out, fold is built in to SML as foldr. A version that associates to the right is called foldl. Just to show some of the other things you can do, consider that you could write map as a partial application of fold. As in:
	fun map f l = 

------------------------

As you start to program using higher-order techniques you quickly find that three more simple higher-order functions can help quite a bit. Those functions are

------------------------

curry converts a pairs function to take arguments individually.

For instance, we have written the curried addition function as:

	fun add x y = x + y;
But this is just a case of a more general process. We can define curry as a function that will work on any pair-based function:

	fun curry f = 

Now with curry we can just say:

	val add = 
In other words, we are just assigning the name add to the curried version of the integer plus operator.

------------------------

Similarly, uncurry converts a curried function to a pairs function:

	fun uncurry f = 
Now, we can define:
val plus = uncurry add;

It should be easy to show that for any curried function f

	curry (uncurry f) = f
and for any pairs function f,
	uncurry (curry f) = f
------------------------

The function compose takes two functions and returns another function that is equivalent to the two of them combined.

For example, suppose you have a function squares that will square all the items in a list, defined as:

	val squares = map 
and another function sum that adds up the elements of a list, as in:
	val sum = 
If we want to define a function sum_squares you can't just say
	val sumSquares = sum squares;
because it's ill-formed in terms of the types (why?). So, we need to write:
	fun sumSquares l = sum (squares l);
With compose, however, we can just write:
	val sumSquares = compose sum squares; 
That is, in general:
(compose f g) x == f (g x)
Compose itself is just defined as:
	fun compose f g =                              ;

You should make sure you understand the types of all these functions.

While curry and uncurry are not built in, compose is built in as the infix operator o. So you would really write:

	val sumSquares = sum o squares;

------------------------

The next thing I want to talk about is the question of how names are scoped in ML. At present we have only seen two kinds of names: global ones and local formal parameter names.

Suppose we define the following names:

	val x = 3;
	fun addx y = x + y;
	addx 5;
Now, what happens if we "redefine" x?
	val x = 7;
	addx 5;








While this may seem strange at first, it is actually a much more consistent behavior than in imperative languages. After all, if you write:
	x = 3;
	y = x + 2;
	x = 4;
	cout << y;
in C++, you don't expect the system to print 6 do you?

Note that this rule applies to all names. If I define a function, then another function which uses that function, and then I decide to redefine the original function, the second function will still continue using the original definition of the first function, even though the new definition is the one available at the top level:

	fun add1 x = x + 1;
	fun add2 x = add1 (add1 x);
	
	add1 4;
	add2 4;
	
	fun add1 x = x + 3;
	
	add1 4;
	add2 4;
	
	
------------------------

Often we want to define a name so that it is local to some scope. I.e., to name some intermediate result in a function, or to name an auxilliary function.

There are two main ways to restrict the scope of a name in ML: The let construct, and the local construct. The syntax for them is as follows:

The let construct is written:

	let
	   definition1
	   definition2
	   ...
	in 
	   expression
	end
The local construct is written:
	local
	  definition1
	   definition2
	   ...
	in 
	   definition>
	end
and it is used anywhere you are making a definiton, rather than computing a value.

------------------------

The local function is most often used to restrict access to some auxillary function. For example:

	local
	   fun factAux result i n =  
	          if (i > n)
	            then result
	            else factAux 
	                    (i * result)
	                    (i+1) n;
	in
	   fun fact n = factAux 1 1 n;
	end;

This way the user has access to fact but has no idea fact_aux exists. Notice the compiler keeps its mouth shut concerning fact_aux.

(Note: The functionality of the local declaration is largely subsumed by the functionality of the module system, and thus it is not much used in actual practice.)

------------------------

The let construct can be used either to provide temporary names, or to hide a function definition. For example, before we defined filter as:

	fun filter p nil = nil
	  | filter p (h::t) = 
	       if p h
	         then h::(filter p t)
	         else filter p t;
We could save a little typing, and perhaps make things clearer by writing:
	fun filter p nil = nil
	  | filter p (h::t) = 
	      let
	         val filter_t = filter p t;
	      in 
	         if p h
	           then h::filter_t
	           else filter_t
	      end;
As an example of hiding a function, we can use let in much the same way as local:

	fun fact n = 
	      let
	         fun factAux result i n =  
	                if (i > n)
	                  then result
	                  else factAux 
	                         (i * result)
	                         (i+1) n
	      in
	         factAux 1 1 n
	      end;

Because the sub-function is in the scope of the parameters of the outer function we do not actually need to pass n around since its value does not change:

	fun fact n = 
	      let
	         fun factAux result i =  
	                if (i > n)
	                  then result
	                  else factAux 
	                         (i * result)
	                         (i+1)
	      in
	         factAux 1 1
	      end;

------------------------

While the let and local constructs enable us to restrict the scope of names in one way, another issue in programming-in-the-large is being able to divide up the global name space so that names used in different global contexts do not conflict. This is the purpose of adding a module system to a language.

The SML module system provides a clean mechanism for gathering related pieces of code together and for controlling which parts of the code are accessible from the outside. At the same time, all the internal code can be easily tested at the top-level with little or no extra work required for final packaging.

The SML module system is built on three pieces: structures, signatures, and functors. Here we will describe just the most basic part of the system, structures, which correspond to the basic notion of module provided in most other languages.

A structure is simply a collection of type and value definitions gathered together so that they may be loaded together, and accessed through one name.

A structure declaration has the form:

	structure structureName =
	struct
	   definition1
	   definition2
	   ...
	end
For example, we might gather together the last three useful higher-order functions into a single structure name ho
	structure ho =
	struct   
	   fun compose f g = fn x => f (g x)
	   
	   fun curry f = fn x => fn y => f (x,y)
	   
	   fun uncurry f = fn (x,y) => f x y   
	end

When the structure is loaded into SML, the system echos back the name of the structure, and the names of all the defined objects and their types.

These values are not available at the top-level. Rather, the items defined in the structure must be accessed through their fully qualified names. which are formed by prepending the name of the structure and a dot, as in ho.curry.

------------------------
This page copyright ©2000 by Joshua S. Hodas. It was built on a Macintosh. Last rebuilt on Monday, January 31, 2000.
http://www.cs.hmc.edu/~hodas/courses/cs131/lectures/lecture04s.html