Loops and Iterators

Sather - A Language Tutorial
Prev |  | Next

# Chapter 3. Loops and Iterators

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.

Prev | Home | Next
A Running Example: Employees |  | Defining Iterators