Loops

Computers are excellent are performing the same task over and over and over again. In order to ensure that it’s just the computer doing repetitive tasks (and not also the coder typing them out), most languages employ a type of operation called a loop. Loops come in two main flavors: definite and indefinite.

Definite loops

Definite loops are ones in which you specify exactly how many times a certain process should happen. Just like in Forth, you specify these loops with DO...LOOP. Let’s look at an example:

fr> 5 0 DO ." Hello!" CR LOOP
Hello!
Hello!
Hello!
Hello!
Hello! 
ok.

These types of loops have five main components: END START DO xxx LOOP. END and START specify the ending and starting values for our loop counter. Think of it like counting: we start at START and begin counting up to END. For every number we count, we do the stuff between DO and LOOP.

In this case, we’ve defined a loop with 5 0 DO ... LOOP. This means that we’ll start at 0 and count up to 5. At each value (0, 1, 2, 3, 4), we do the middle stuff, which in this case is printing Hello!. Note that we stop at END without doing the stuff again.

It’s often useful to be able to do stuff with the number we’re currently at. For instance, what if we wanted to print out what number we’re currently counting at before saying Hello!? In this case, we use the special word I, which copies the current value of the loop counter to the stack.

fr> 5 0 DO I . ." Hello!" CR LOOP
0 Hello! 
1 Hello! 
2 Hello! 
3 Hello! 
4 Hello! 
ok.

The LOOP command tells froth to add 1 to the loop counter. This means that you can use negative numbers and/or fractions as loop limits:

fr> 0 -2 DO ." looping!" LOOP
looping! looping! ok.
fr> 3.5 0.5 DO ." fraction!" LOOP
fraction! fraction! fraction! ok.

Like with the examples in Chapter 1, you can make words that expect to find elements on the stack as input to a loop:

fr> : MULTIPLICATION ( n -- ) cr 11 1 do dup i * . loop drop ;
ok.
fr> 7 multiplication
7 14 21 28 35 42 49 56 63 70 ok.

More Complex Loops

if...then statements can be nested within loops to make the computer do certain operations on certain loop iterations. For example:

fr> 64 0 do i 8 mod 0= if cr then ." *" loop
* * * * * * * * 
* * * * * * * * 
* * * * * * * * 
* * * * * * * * 
* * * * * * * * 
* * * * * * * * 
* * * * * * * * 
* * * * * * * * ok.

This loop proceeds from 0 to 64. If the loop counter is equivalent to a multiple of 8 (i 8 mod 0= if), then it prints a newline. Either way, it prints a star.

Just like with if...then statements, we can also nest loops. For example:

fr> : multip ( n -- ) cr 5 1 do dup i * . loop drop ;
ok.
fr> : table ( -- ) cr 5 1 do i multip loop ;
1 2 3 4 
2 4 6 8 
3 6 9 12 
4 8 12 16 ok.

This produces a (small) multiplication table. If you change the loop limits, you can change how big the table is.

The built-in words I copies the current loop counter to the stack. If you wanted to had nested loops and wanted to access the outer loop counter from an inner loop, J copies that value to the stack. K performs a similar operation, but returns the loop counter two levels up. For example:

fr> 2 0 DO 2 0 DO 2 0 DO I J K . . . CR LOOP LOOP LOOP
0 0 0 
0 0 1 
0 1 0 
0 1 1 
1 0 0 
1 0 1 
1 1 0 
1 1 1 
ok.

We have three nested loops. The outermost one executes twice, counting from 0 to 2. The middle loop executes twice per outer loop, counting from 2 to 4 each time. The inner loop executes twice per middle loop, counting from 4 to 6 each time. In all, the outer loop runs twice, the middle loop 4 times, and the inner loop 8 times. From the innermost loop, we use I to get the loop counter, J to get the second loop counter, and K to get the outermost loop counter.

We also don’t have to always increment the loop value by 1. Instead, we can use the +LOOP word, which expects a value on the stack and increments the loop counter by that amount. For example, we can modify our previous triple loop:

fr> 10 0 DO 4 0 DO 0 2 DO I J K . . . CR -1 +LOOP 2 +LOOP 5 +LOOP
0 0 2 
0 0 1 
0 0 0 
0 2 2 
0 2 1 
0 2 0 
5 0 2 
5 0 1 
5 0 0 
5 2 2 
5 2 1 
5 2 0 
ok.

Now the outermost loop iterates from 0 to 10 by 5 (10 0 DO ... 5 +LOOP), the second loop iterates from 0 to 4 by 2 (4 0 DO ... 2 +LOOP), and the innermost loop iterates from 2 to 0 by -1 (0 2 DO ... -1 +LOOP).

Do you see what’s changed? There’s one key difference between the outer two loops and the inner loop of the previous example. The outer loops iterate as long as they’re less than the end value, but the inner loop iterates as long as its greater than or equal to the end value. When you use a loop with a negative +LOOP value, keep in mind that the loop will iterate an additional time compared to the positive direction. For example, 5 0 DO ... 1 +LOOP iterates 5 times, whereas 0 5 DO ... -1 +LOOP iterates 6 times.

Indefinite Loops

Indefinite loops keep going without a specified end point. These loops use BEGIN...UNTIL rather than DO...LOOP. The basic syntax is the same, except that UNTIL will check the stack for TRUE. If it finds TRUE, the loop terminates, and otherwise execution jumps back to BEGIN.

fr> 0 BEGIN 1 + dup dup . 10 > UNTIL
1 2 3 4 5 6 7 8 9 10 11 ok.

Here we start with 0, then at each iteration of the loop we add 1 to it. We then duplicate it twice, print one of the copies, and then let UNTIL check if the second copy is greater than 10. Once the value becomes 11, 10 > evaluates to TRUE and the loop exits.

UNTIL checks the condition at the end of the loop, but what if we wanted to check at the beginning? BEGIN...UNTIL loops will always execute at least once, but sometimes it’s preferable to have a loop that doesn’t always execute.

For this, we have BEGIN...WHILE...REPEAT loops. WHILE examines the stack similar to UNTIL, and skips immediately to REPEAT if the condition is TRUE.

Let’s try the previous example again, using a WHILE loop instead:

fr> 0 BEGIN dup 10 < WHILE dup . 1 +  REPEAT
0 1 2 3 4 5 6 7 8 9 ok.

There are a few differences here. First, we’ve had to reverse the comparison sign. This is because this loop continues while the condition is true, whereas BEGIN...UNTIL goes until the loop is true.

In some particular cases, you need a loop that never ends. The BEGIN...AGAIN loop will run forever without terminating. I’d include an example, but it would never stop running…

So in the absence of that, I’ll showcase use of BEGIN...AGAIN alongside another operator, LEAVE. LEAVE exits the current loop immediately. For example:

fr> 0 begin 1 + dup dup . 10 > if leave then again
1 2 3 4 5 6 7 8 9 10 11 ok.

Normally the BEGIN...AGAIN loop would go on forever. However, in the middle we’re doing 10 > if leave then. This compares the current value on the stack to 10, and if it’s greater than 10, we run leave. As soon as the value on the stack reaches 11, we execute leave and the loop finishes.

Words in this chapter