Register machines use jumps. Modern programming languages instead use structured control flow: sequences, conditionals and loops. We study two minimal structured languages, introduced by Meyer and Ritchie (1967): LOOP, whose only loop is a for loop executed a number of times fixed on entry, and WHILE, which adds a while loop. The distinction between the two loops corresponds exactly to the distinction between primitive recursion and minimization.
9.1 Syntax and semantics
Programs manipulate variables , each holding a natural number.
The WHILE programs are generated by the grammar
The LOOP programs are the WHILE programs which contain no instruction.
A state is a function with for all but finitely many ; is the value of . A program denotes a partial function on states.
The semantics of a program is the partial function from states to states defined by induction on :
- , , ;
- ;
- where and is the -fold iterate of ;
- for the least such that and , provided with for every ; it is undefined if there is no such .
In a instruction, the number of iterations is the value of on entry: modifying inside the body does not change it. In a instruction, the condition is re-evaluated before each iteration.
A program computes the partial function defined by , where for and otherwise. In words: the input is placed in , all other variables are , and the output is read in . A function is LOOP-computable (resp. WHILE-computable) if it is computed by a LOOP (resp. WHILE) program.
For every LOOP program , is total. Hence every LOOP-computable function is total.
By induction on : assignments are total, composition of total functions is total, and a finite iterate of a total function is total.
9.2 Programming in LOOP
We use abbreviations, each of which stands for a LOOP program using fresh auxiliary variables.
- Addition : .
- Predecessor : . After iterations, and .
- Conditional : compute in a fresh variable (by a loop setting to at each iteration), then .
9.3 LOOP programs compute the primitive recursive functions
A function is LOOP-computable if and only if it is primitive recursive.
We prove the two directions separately. In the first direction, programs for subfunctions are combined using fresh auxiliary variables, so that they do not interfere with each other.
Primitive recursive ⇒ LOOP. We show by induction on a derivation of that for all distinct indices there is a LOOP program, written , which, started in any state , sets to and modifies no variable other than and a finite set of auxiliary variables, which may be chosen disjoint from any given finite set of indices.
- Basic functions: , then , and .
- Composition : choose fresh , and run , with auxiliary variables of each subprogram chosen disjoint from , , .
- Primitive recursion, , : with fresh , , , run
By induction on the number of iterations, after iterations and . For , where is a constant , the first assignment is followed by instructions . The subprograms never rely on the initial values of their auxiliary variables, since each of these is assigned before being read; this is why they can be run repeatedly inside the loop. (Writing is allowed: the program for reads and writes into a fresh variable, which is then copied into .)
The function itself is computed by , whose auxiliary variables are chosen distinct from .
LOOP ⇒ primitive recursive. Let be a LOOP program computing , and let be larger than and than the indices of all variables occurring in . Variables of index are not modified by and do not influence its effect on the others. We code the relevant part of a state by the sequence code of the lesson Coding finite sequences, and define as the code of the state reached from . We show by induction on subprograms of that is primitive recursive (on codes of length ):
- assignments: , , ;
- sequence: ;
- loop: where and , a primitive recursion.
Finally , with entries in the code, is primitive recursive.
9.4 WHILE programs compute the partial recursive functions
A partial function is WHILE-computable if and only if it is partial recursive.
Partial recursive ⇒ WHILE. We extend the first part of the previous proof. The constructions for composition and primitive recursion are unchanged; with partial functions, the program simply does not terminate when one of the subprograms does not, which matches the conventions for partial functions. For minimization, , with fresh and :
This program evaluates in order and stops at the first zero; it does not terminate if one of these computations does not, or if there is no zero. This is exactly the definition of minimization.
WHILE ⇒ partial recursive. As in the LOOP case, define for each subprogram the partial function on codes of states, and show by induction that it is partial recursive. Assignments, sequences and instructions are handled as before, now with partial functions (the iteration is defined by primitive recursion for partial functions). For , let and . Then
By the definition of minimization, the search succeeds at exactly when is defined with for , and is defined with : this matches the semantics of the instruction.
Every WHILE program is equivalent, for the computation of functions, to a WHILE program containing a single instruction.
The function computed by the program is partial recursive. By Kleene’s normal form (Theorem 8.6, proved in the lesson Equivalence of the models without using this corollary), it can be written with and primitive recursive. By Theorem 9.5, and are computed by LOOP programs, and the construction above for the minimization uses one instruction.
This is related to the Böhm–Jacopini theorem (1966): every flow diagram can be rewritten using only sequencing, conditionals and while loops, at the cost of auxiliary variables. The form in which a single while loop suffices is the “folk” version of this theorem discussed by Harel (1980).
9.5 WHILE programs and register machines
Every WHILE-computable partial function is URM-computable.
Let compute , and let be larger than and than the indices of all variables of . The variable , for , is represented by the register ; the register is kept equal to and serves for unconditional jumps and for tests against . Each occurrence of a instruction in receives two registers of its own, with indices larger than . For each subprogram of we define a URM program in standard form, by induction on ; let denote the length of the translation of the body.
- , , .
- is the concatenation of Definition 4.7.
- is , followed by with all jump targets increased by , followed by .
- , with the registers of this occurrence, is , , , followed by with all jump targets increased by , followed by , .
In each case the jump targets are at most the length plus one, so is in standard form. We show by induction on : if is started at its first instruction in a register state with and for all , then halts if and only if , and in that case it halts in a state with and for all . Assignments are immediate, and sequences follow from Lemma 4.8. For a instruction, by induction on , the program reaches its first instruction for the -th time if and only if and for all , and then the registers represent ; since , the first jump exits exactly when the represented value of is , as in Definition 9.2. For a instruction, holds the value of on entry and is not modified by , which only uses the registers of the occurrences inside ; by induction on , the test is reached for the -th time with and the registers representing , so the program exits after iterations.
The program for consists of , which move the input to the registers of (in decreasing order, so that no input is overwritten before it is copied), then , , then , then . After the first part, and holds for every , and the registers of the occurrences hold (they are not used before being set anyway). By the claim, the program halts if and only if , and then contains .
9.6 Exercises
Write LOOP programs computing , , and .
Solution
: .
: (each round multiplies by ).
: .
Show that the functions computed by LOOP programs of nesting depth at most (no inside a ) are bounded by linear functions: for each such program computing there are with . Deduce that multiplication requires nesting depth at least .
Solution
Consider a state and let . Each assignment increases the maximum of the values by at most . A loop-free program with instructions therefore maps a state of maximum to one of maximum at most . A loop with loop-free , entered in a state of maximum , runs at most times, so the maximum becomes at most . A sequence of such instructions multiplies the maximum by at most a constant and adds a constant. Hence . Multiplication is not linearly bounded (), so it needs nested loops. (Meyer and Ritchie studied the classes of functions computed by LOOP programs of nesting depth at most . For , is the level of the Grzegorczyk hierarchy, which stratifies the primitive recursive functions by growth rate.)
Show directly, without using partial recursive functions, that every URM-computable partial function is WHILE-computable, by a WHILE program with a single instruction. (The converse is Proposition 9.8.)
Solution
Let compute , and let be the largest of and the register indices of the program. Represent by for , and use fresh variables (the index of the next instruction) and . The program is: ; ; ; . The body executes the instruction of index and updates : it first copies into a fresh variable , then performs a sequence of conditionals “if then execute ”, where , , become , , followed by , and sets to if and to otherwise. Since the conditionals test , which is not modified, exactly one of them applies in each pass (when , ). At the end of , . Equality tests, and the conditionals are LOOP-definable, by Theorem 9.5 and the conditional of the section on programming in LOOP. By induction on the number of steps, after passes through the variables hold the configuration of the URM after steps, so the loop terminates exactly when the URM halts, with in .
9.7 Notes and references
LOOP programs and the characterization of the primitive recursive functions are due to Meyer and Ritchie, who also related the nesting depth of loops to the Grzegorczyk hierarchy. The elimination of jumps in favor of structured control flow is the subject of the Böhm–Jacopini theorem; Harel discusses its folk versions and their history.
- A. R. Meyer, D. M. Ritchie, The complexity of loop programs, Proceedings of the 22nd ACM National Conference (1967), 465–469.
- C. Böhm, G. Jacopini, Flow diagrams, Turing machines and languages with only two formation rules, Communications of the ACM 9 (1966), 366–371.
- D. Harel, On folk theorems, Communications of the ACM 23 (1980), 379–389.