(* * This file contains the definition of a ForLoops module and some testing of * its functions. The primary function defined in ForLoops is named for, which * implements a functional for-loop. It performs a computation comparable to * the following C for-loop: * * for(i = ; i <= ; i += ) { * ; * } * * or the equivalent Modula-2 (Ada, Pascal) for loop * * for i:= to by do * * end; * * The for function has the following signature: * * fun for(start:int, until:int, by:int, vars:'x, body:int*'x->'x):'x list; * * The first three parameters are those of a typical imperative for loop -- * , , and as integers. The fourth parameter, vars, holds * the loop working vars, as discussed further below. The final input is the * for-loop , defined as a function whose signature we'll discuss * shortly. The output of the for function is a list of for-loop results. * * To understand the workings of a for-loop in a functional language, we need * to consider carefully what the of a for loop does in an imperative * language. To whit: * * (1) it uses the value of the for variable to perform some computation * (2) the computation typically involves additional working variables * (3) the computation produces a value, stored in one or more of the * working variables, and these variables become the result of the loop * computation. * * In the functional version of a for-loop, the is a function. As such, * it must take all of its external working values as inputs, including the * value of the for variable. Further, the body function cannot produce its * result via side effects on some result variable, but rather must produce its * results as functional output. The output results include any changes to the * working variables made within the body of the loop. * * These observations help to explain the type of the body parameter, which is * the function signature int*'x->'x. In terms of the three points above, the * elements of this signature are used as follows: * * (1) the integer input contains the current value of the for variable * (2) the for-loop working variables are of generic type 'x * (3) the output of the function body is also of type 'x, so that any * working variable changes are output by the body, and at the end of * the loop, the result variables have been computed. * * In an imperative language, the for-loop statement does not itself produce a * value. That is, the of the loop computes a value, but the loop * itself does not. E.g., the following C program is illegal, because the C * for-loop does not return a value: * * int i,k,result; * k = 2; * result = 1; * x = for (i=1; i<=10; i++) result *= i+k; * * In this style of accumulating for-loop, it might well make sense to have the * for-loop return a value, but this is not the case in C. * * In the functional for-loop, we could mimic the imperative behavior by having * the for function return nil or unit. However, this does not make good * sense, because there would be no way for the for-loop body to communicate * its results. I.e., while the imperative for-loop communicates its results * through some change to a (persistent) variable, the functional for-loop * cannot effect such variable changes, so it must return its results as a * return value. It should be noted that the case where an imperative loop * changes more than one variable is handled in the functional for-loop by * having the body return some structured value, such as a tuple or list. * * In addition to not producing a value as an output, the imperative for-loop * does not accept any values as inputs. In an imperative language, this fact * does not usually occur to programmers. I.e., what would the "inputs" to a * for loop be? Well, if we closely examine the body of a C for-loop, we * observe that it is in fact a scope, and within this scope there are * references to global variables. For example, in the C program just above, * the variables i, k, and result are global to the scope of the for-loop body. * But such global variable references are not useful in a functional for-loop. * Hence, the for-loop *function* must take as inputs any values that the * for-loop *body* needs in its computation. This observation explains the * need for the the vars input parameter. This input is sent in to the loop * body function, along with the value of the for variable. * * The preceding discussion explained the input parameters to the for function. * We now consider for's output, which is declared as type * * 'x list * * where type 'x is the return type of the body function. Based on what we * have discussed thus far, we might expect the return value of the for * function to be just of type 'x, not a list of 'x. That is, the for function * could return the final results computed by its body function, not a complete * history. * * There are couple of good reasons to return a list from for, rather than a * single result. First, returning a list makes for a more general function, * in that a complete history of computation is returned. If the user of for * wants the last value of the history, she simply extracts the last value of * the list. This is in fact done in the companion for1 function defined * below. * * The second reason for returning a list from for becomes apparent when we * consider the return value from a for-loop that executes zero times. This * should be some form of empty or null value. But, as has been noted * elsewhere, there is no universal "null" data value in ML. The closest we * come to it in ML is the nil value for list types. Hence, if we return ('x * list) from for, instead of just 'x, we can use nil to represent the value of * a for loop that performs no computation. * * The proper alternative to the use of nil is to raise an exception. This is * in fact what for1 does, as can be seen in its definition below. * * We're now ready for the definition of the for and for1 functions, which are * really quite simple. The reader will probably notice the spatial * inefficiency of for1 -- it computes the head of the list returned by for. A * more space-efficient version of for1 is left as an exercise for the reader. * In addition to for and for1, there is a foreach function, that iterates over * the elements of a list, calling a body function on each iteration. * *) structure ForLoops = struct exception EmptyLoop; (* * General purpose for-loop, returning a list of all loop iterations. *) fun for(start: int, until, by, vars:'v, body:int*'v->'v): 'v list = if start>until then [] else let val result = body(start, vars) in for(start+by, until, by, result, body) @ [result] end; (* * A la for, but returns only the result of the last iteration. *) fun for1(start, until, by, vars, body) = hd(for(start, until, by, vars, body)) handle Hd => raise EmptyLoop; (* * C-Shell-style list iterator. *) local fun foreach1(l:'x list, vars:'y, body:'x*'y->'y):'y list = if null(l) then [] else let val result = body(hd(l), vars) in foreach1(tl(l), result, body) @ [result] end in fun foreach(l, vars, body) = hd(foreach1(l, vars, body)) handle Hd => raise EmptyLoop end; end; open ForLoops; (* * The ML expression that follows is equivalent to the following C for-loop: * * for (i=1; i<=10; i+=2) { * printf("%d\n", i); * } * * or the following Modula-2 (Adaish, Pascalish) for-loop * * for i:=1 to 10 by 2 do * WriteInt(i,3); WriteLn; * end; * * Note well the use of an anonymous (lambda) function to define the for-loop * body. Chapter 11, pp. 103-4 of Ullman's ML discusses anonymous functions in * general. *) for(1, 10, 2, nil, fn(i,x)=> (print(Int.toString(i)); print("\n"); nil) ); (* * The ML expression following this comment is the equivalent of the (illegal) * C program discussed in the earlier comments. Here's that example, this time * in legal C: * * int i,k,result; * k = 2; * result = 1; * for (i=1; i<=10; i++) { * result *= i+k; * } * * The similarity in style and size of the ML and C versions of this loop is * noteworthy, though it may take the C programmer a while to "appreciate" the * ML version. * *) for1(1, 10, 1, 1, fn(i, result)=> let val k = 2; val result = result * (i + k) in result end ); (* * The preceding example would more likely be written as follows in C: * * int i,k,result; * for (i=1, k = 2, result = 1; i<=10; i++) { * result *= i+k; * } * * which has the following comparable form in ML: *) for1(1, 10, 1, (2, 1), fn(i, (k, result)) => let val result = result * (i + k) in (k, result) end ); (* * Additional example uses of for loops follow. *) for(1, 10, 1, 1, fn(i, result) => let val k=2; val result = result * (i + k) in result end ); (* * Sum the elements of a list. *) foreach ([1,2,3], 0, fn(x,result)=> let val result = result+x in result end); (* * Compute some stats on the element of a list (sum, product, count, average).. *) val l = [1,2,3,4,5,6,7,8]; val (sum, product, count, avg) = foreach (l, (0,1,0,0.0), fn(item, (sum, product, count, avg:real)) => let val sum = sum + item; val product = product * item; val count = count + 1; val avg = real(sum)/real(count); in (sum, product, count, avg) end);