The Sisal for-loop contains three parts: the range, the body, and the returns clause. The syntax looks like this:
for {range} {optional body} returns {returns clause} end forThe range specifies the set of values controlling the number of instances of the body and returns clause that will be generated. Typically, it consists of an index identifier and a set of numeric values, such as "i in 1, n," but it can also consist of a pair of names for an element and an aggregate, such as "a in counters". In these ways, the number of instances of the loop that will be calculated is established, as well as the specific information needed by each one.
The loop body is optional, and may contain the definitions of the values that are to be returned or any intermediate values the calculation needs.
The returns clause controls which and how many data values will be returned by the loop, and it contains one or more aggregation or reduction operators (which will look familiar). But before we explain them, let's look at a simple for-loop that increments an array of counters, to see how these elements interact in practice.
for i in 1, num_counters new_count := counters[i] + increment[i] returns array of new_count end forHere we see a loop whose range is from 1 to the value named "num_counters;" we assume this value exists and makes sense in the present context. In the second line of the loop we see this loop's body. In this loop, the body is where all the work is done, namely the addition of the elements of the two vectors "counters" and "increment." Both vectors are assumed to be of such size that there will be an element of each available for the entire range of values of the loop index, "i." For each value of this index, a new value is calculated, named "new_count," and all instances of this calculation are independent. Finally, in the returns clause, all the instances of "new_count" are aggregated into an array, and that array is returned as the value of the loop.
Recalling that its index range is an integral part of a Sisal array, we must specify the index range of the array returned by this loop. This is simple and automatic; it is simply the set of values in the loop's range. In the current example, the loop's returned array has range [1, num_counters]. This is characteristic of all Sisal for-loops that return arrays, except for cases we will discuss in a future section on advanced looping concepts.
As before, the for-loop is an expression, and has a well-defined value, which is the value defined by its returns clause. In the above case, it is an array. However, we can also return reduced values, such as in the following loop:
for i in 1, num_counters new_count := counters[i] + increment[i] returns value of sum counters[i] value of sum new_count array of new_count end forThis loop returns three values: a scalar (the sum of all the values in the array "counters"), an array (containing all the values of "new_count"), and another scalar (the sum of all the values of "new_count"). This demonstrates aggregation and reduction in the same loop
The body of a for-loop is similar to a let-in statement, in that temporary names can be defined in it that are used subsequently in the loop and/or in the returns clause. But strictly speaking, the loop body is not generally needed at all. The following two loops have the same value as the one immediately above:
for i in 1, num_counters old_count := counters[i]; new_count := old_count + increment[i] returns value of sum old_count value of sum new_count array of new_count end for for i in 1, num_counters returns value of sum counters[i] value of sum counters[i] + increment[i] array of sum counters[i] + increment[i] end forThe first of these two loops demonstrates that names, such as "old_count" may be assigned to values within the loop, and then used both in the loop and in the returns clause. Again, a different instance of "old_count" is generated for each value of the loop's range, and none of the instances can affect each other in any way outside of the loop's returns clause. This guarantees that no matter how many or how few of the loop instances are computed in parallel, or in what order they are computed, that same set of intermediate values will be generated and the same set will be returned as the value(s) of the loop itself.
The second of the above loops demonstrates that all the work can occur in the loop's returns clause, leaving an empty loop body. This in no way changes what we just said: each instance of the loop's calculation is independent of all the others, and they can occur in any order and give the same results.
There is one more thing to consider before we leave this section, and that is the alternate form of range generator we alluded to above - the one containing a pair of identifiers. Simply speaking, if we don't need the loop index as a numerical value, we don't have to use it. We can instead refer by name to the individual elements of the array we are working on, as in the following example:
for elt in counters new_count := elt + 1 returns value of sum elt value of sum new_count array of new_count end forThis loop assumes that the same value will be used to increment each element of the array "counters," namely one. So, no loop index is needed, since each instance of the loop gets one instance of "elt" from the array "counters" to work with. More advanced forms of loop range generation are also possible, and we will discuss them in a later section.