GIML: Lesson Three

Curry

A function of more than one argument may be implemented as a function of a tuple or a "curried" function. (After H B Curry). Consider the function to add two integers Using tuples - fun add(x,y)= x+y : int; val add = fn int * int -> int The input to this function is an int*int pair. The Curried version of this function is defined without the brackets or comma: - fun add x y = x+y : int; val add = fn : int -> int -> int The type of this function is int->(int->int). It is a function which takes an integer and returns a function from an integer to an integer. We can give both arguments without using a tuple - add 2 3; it = 5 : int Giving one argument results in a "partial evaluation" of the function. For example applying the function add to the number 2 alone results in a function which adds two to its input: - add 2; it = fn int-> int - it 3; it = 5 : int Curried functions can be useful - particularly when supplying function as parameters to other functions.
This would be a good time to consider the Diversion: Mandelbrot

Pattern Matching

Note for Moscow ML users

In the examples so far we have been able to define functions using a single equation. If we need a function which responds to different input we would use the if _ then _ else structure or a case statement in a traditional language. We may use if then else in ML however pattern matching is preferred. Example: To change a verb from present to past tense we usually add "ed" as a suffix. The function past does this.

past "clean" = "cleaned" past "polish" = "polished" There are irregular verbs which must be treated as special cases such as run -> ran. fun past "run" = "ran" | past "swim" = "swam" | past x = x ^ "ed"; When a function call is evaluated the system attempts to match the input (the actual parameter) with each equation in turn. Thus the call past "swim" is matched at the second attempt. The final equation has the free variable x as the formal parameter - this will match with any string not caught by the previous equations. In evaluating past "stretch" ML will fail to match the first two equations - on reaching the last equation x is temporarily bound to "stretch" and the right hand side, x^"ed" becomes "stretch"^"ed" evaluated to "stretched".

In the following examples we use exactly two patterns for our functions. The first pattern is the base case which is typically 0 or 1 the second is n which matches with all other numbers.
A typical function takes the form:
fun f(0) = ??The equation used when the input is zero
| f(n) = ??The equation used when n is 1 or 2 or 3 ...

More on pattern matching later....

Recursion

Using recursive functions we can achieve the sort of results which would require loops in a traditional language. Recursive functions tend to be much shorter and clearer. A recursive function is one which calls itself either directly or indirectly. Traditionally, the first recursive function considered is factorial. n n! Calculated as 0 1 1 1*0! = 1*1 = 1 2 2*1! = 2*1 = 2 3 3*2! = 3*2 = 6 4 4*3! = 4*6 = 24 5 5*4! = 5*24 = 120 6 6*5! = 6*120 = 720 7 7*6! = 7*720 = 5040 ... 12 12*11*10*..2*1 = 479001600 A mathematician might define factorial as follows
0! = 1
n! = n.(n-1)! for n>0
Using the prefix factorial in place of the postfix ! and using * for multiplication we have fun factorial 0 = 1 | factorial n = n * factorial(n-1); This agrees with the definition and also serves as an implementation. To see how this works consider the execution of factorial 3. As 3 cannot be matched with 0 the second equation is used and we bind n to 3 resulting in factorial 3 = 3 * factorial(3-1) = 3*factorial(2) This generates a further call to factorial before the multiplication can take place. In evaluating factorial 2 the second equation is used but this time n is bound to 2. factorial 2 = 2 * factorial(2-1) = 2*factorial(1) Similarly this generates the call factorial 1 = 1 * factorial 0 The expression factorial 0 is dealt with by the first equation - it returns the value 1. We can now "unwind" the recursion. factorial 0 = 1 factorial 1 = 1 * factorial 0 = 1*1 = 1 factorial 2 = 2 * factorial 1 = 2*1 = 2 factorial 3 = 3 * factorial 2 = 3*2 = 6 Note that in practice execution of this function requires stack space for each call and so in terms of memory use the execution of a recursive program is less efficient than a corresponding iterative program. As functional advocates we take a perverse pride in this.

Take care

It is very easy to write a non-terminating recursive function. Consider what happens if we attempt to execute factorial ~1 (the tilde ~ is used as unary minus). To stop a non terminating function press control C. Be warned that some functions consume processing time and memory at a frightening rate. Do not execute the function: fun bad x = (bad x)^(bad x);