Chapter 3. Loops and Iterators

Table of Contents
3.1. Using iterators
3.2. Defining Iterators
3.3. Iterator Examples

Until now we have only made use of the simple loop constructs, while! and until!. These are similar to the loop constructs found in other languages, aside from the terminal '!'. However, that terminal '!' hides something very special about these loop constructs in Sather; a programmer can define such looping constructs as easily as he or she could define a standard routine. Once defined, they may be used as conveniently as the while! and until! iterators.

To a first approximation, iterators are like streams that can "yield" different values on successive loop iterations. When an iterator has no more values to yield, it "quit"s. This, in turn, terminates the enclosing loop.

Iterators are defined as class features, just like routines, but iterator names must terminate with an '!'. When an iterator is called, it executes the statements in its body in order. If it executes a yield statement, control is returned to the caller. In this, the iterator is similar to a coroutine whose state remains persistent over multiple calls. Subsequent calls on the iterator resume execution with the statement following the yield statement. If an iterator executes quit or reaches the end of its body, control passes immediately to the end of the innermost enclosing loop statement in the caller and no value is returned.

3.1. Using iterators

3.1.1. loop statements

Iteration is done with loop statements, used in conjunction with iterator calls. In the absence of iterator calls, a loop statement simply executes an infinite loop. The difference between an iterator call and a routine call is that the iterator call "remembers" its state after it yields a value and, on subsequent calls, it simply resumes execution. The "lifetime" of an iterator usually includes several calls within a particular loop. Hence, an execution state is maintained for each iterator call textually enclosed within a loop - this execution state will be used to "remember" the state of the iterator between invocations. When a loop is entered, the execution state of all enclosed iterator calls is initialized. When an iterator is encountered, control is transferred to the iterator until the iterator "yields" control. Just as a routine may provide a value when it returns, so too an iterator may provide a value when it yields.
sum: INT := 0;
loop sum := sum + 1.upto!(10); end;
#OUT + sum + '\n';              -- Prints sum of integers from 1 to 10

Instead of yielding control back to the enclosing loop, an iterator may also terminate or quit, which terminates the enclosing loop.

Note that each loop may contain more than one iterator call, thus providing much more flexibility than conventional languages. When any of the iterators terminates, the whole loop terminates, and execution continues at the next statement after the loop.

3.1.2. Built-in iterators

The until!, while! and break! iterators are built-in. They have the standard definitions of until, while and break in other languages and may occur anywhere in the loop body. while! expressions are iterator calls which take a single boolean argument that is re-evaluated on each iteration. They yield when the argument is true and quit when it is false. until! expressions are iterator calls which take a single boolean argument that is re-evaluated on each iteration. They yield when the argument is false and quit when it is true. break! expressions are iterator calls which immediately quit when they are called.
sum:INT := 0;
i:INT := 0;
loop while!(i < 5);
   sum := sum + i;
   i := i + 1;
end;
#OUT+ "Sum=" + sum + '\n';         -- Prints out Sum=10

The break! iterator can be used to terminate a loop at any time. We illustrate this with the bubble sort routine show below, which terminates the first time a pass through the data occurs with no order change.
bubble_sort(a:ARRAY{INT}) is
   loop
      done: BOOL := true;
      i:INT := 0;   -- Loop until the "break!" is encountered
      loop until!(i = (a.size-2));
         if a[i] > a[i+1] then
            done := false
            swap(inout a[i], inout a[i+1]);
         end;
         i := i + 1;
      end;
      if done then
         break!;
      end;
   end;
end;

The 'swap' routine is as we have described earlier.
swap(inout x:INT, inout y:INT) is
   tmp:INT := x;
   x := y;
   y := tmp;
end;

Note that the above 'bubblesort' routine could easily be rewritten to only use until!.

In addition to the built-in iterators, there are many commonly used iterators in the INT class that provide for interation over a range of values. For instance, the iterator upto! yileds successive integer values.
sum:INT:= 0;
loop
   sum := sum + 10.upto!(20);
end;

The upto! iterator returns successive integers from 10 upto 20, inclusive. Below, are examples of a few other common iterators
i:INT := 10;
sum:INT := 0;
loop
   11.times!;
   sum := sum + i;
   i := i + 1;
end;

The 'times' iterator yields a certain number of times. For iterating over a range with a certain stride, use the 'step' iterator
sum:INT := 0;
loop
   sum := sum + 18.step!(11,2);
end;
-- The first argument is the number of iterations, 11 in this case
-- the second argument is the stride

The following example counts 11 even numbers starting at 18

The 'step_upto!' iterator is similar, but instead of specifying a number of iterations, it specifies the maxium value to be reached
sum:INT := 0;
loop
   sum := sum + 18.step_upto!(40,2);
end;

The following loop is equivalent to the preceeding one.