LOOP (programming language)

LOOP is a simple register language designed to precisely capture the primitive recursive functions.[1] The language is derived from the counter-machine model.[2] Like the counter machines the LOOP language comprises a set of one or more unbounded registers, each of which can hold a single non-negative integer. A few arithmetic instructions operate on the registers: inc x (increment), dec x (decrement: ), x = 0 (clear), and x = y (copy). The only control flow instruction is LOOP x: .... It causes the instructions within its scope to be repeated times.[3]

In contrast to GOTO programs and WHILE programs, LOOP programs always terminate.[4] The running time is known in advance. Therefore, the set of functions computable by LOOP-programs is a proper subset of computable functions (and thus a subset of the computable by WHILE and GOTO program functions).[5]

The LOOP language admits several variants, distinguished by their set of basic instructions. Four such variants, L0L3, are distinguished in this article. Despite differences in syntactic convenience and loop-nesting depth, all variants compute exactly the primitive recursive functions. At shallow nesting depths, however, the choice of basic instructions affects which functions are reachable: with predecessor as a primitive (L2, L3), the Kalmár elementary functions are computable at nesting depth 2, and the Presburger-definable functions at depth 1; without predecessor (L0, L1), these correspondences shift upward by one level. An example of a total computable function that is not LOOP-computable at any depth is the Ackermann function.[6]

History

edit

The LOOP language was formulated in a 1967 paper by Albert R. Meyer and Dennis M. Ritchie. They showed the correspondence between the LOOP language and primitive recursive functions, proving that each primitive recursive function is LOOP-computable and vice versa.[7]

The language also was the topic of the unpublished PhD thesis of Ritchie.[8][9][10]

It was presented by Uwe Schöning, along with GOTO and WHILE.[11]

Definition

edit

Several variants of the LOOP programming language have been defined, based on different sets of basic instructions. Despite these variations, they agree on the class of functions they compute: exactly the primitive recursive functions. However, when considering loop nesting depth that is, the number of nested LOOP constructs allowed the choice of basic instructions affects the expressive power at shallow depths.

Four variants of the LOOP language are distinguished here:

  • L0: Minsky (1967);[12]
  • L1: Meyer and Ritchie (1967),[7] Tsichritzis (1970),[13] Machtey (1972),[14] Beck (1975),[15] Fachini & Maggiolo-Schettini (1979),[16] Goetze and Nehrlich (1980),[17] Calude (1988),[18] Schöning & Pruim (1998),[19] Tourlakis (2012),[20] Matos (2015);[21]
  • L2: Tsichritzis (1971),[22] Beck (1975),[15] Kfoury et al (1982),[23] Odifreddi (1989),[24] Matos (2014);[25]
  • L3: Cherniavsky (1976),[26] Cherniavsky and Kamin (1979),[27] Handley and Wainer (1999).[28]

In this presentation, the term "LOOP program" refers collectively to any of L0L3.

Syntax

edit

A LOOP program consists of a sequence of instructions, which modify a finite number of registers, any of which may contain a non-negative integer.

If x and y are registers, define the following sets Bi of basic instructions:

  • B0 = { x = 0, inc x }
  • B1 = B0 { x = y }
  • B2 = B0 { dec x }
  • B3 = B1 B2

For we define the loop language Li as the smallest set of programs generated by the following rules:

1. Every instruction s ∈ Bi is a program in Li.

2. If P and Q are in Li, then the sequential composition

P
Q
is in Li.

3. If P ∈ Li, then

LOOP x:
    P
is in Li.

Indentation determines block structure in a Python-like notation.

A LOOP program may be associated with a signature specifying input and output registers:

signature ::= 'def' program_name '(' input ')' '->' '(' output ')' ':'
input     ::= [ register (',' register)* ]
output    ::= register (',' register)*
Notes
  • The signature is not part of the program.
  • As and can be any lists of registers, in general P can compute several functions, depending on the chosen mapping.[29] So multiple signatures can be associated to a LOOP program.
  • In this presentation an intended signature is placed before the program.

Semantics

edit

Registers range over . Execution proceeds sequentially. A loop of the form LOOP x: ... executes its body exactly times, where is the value of the register at loop entry.[3]

A LOOP program P is said to compute a function when:

  1. , an m-tuple, specifies registers which contain the arguments of ; the remaining registers contain ;
  2. , an n-tuple, specifies registers;
  3. after the execution of P the output registers contain .[30]
Note
  • When specifies a single register, P defines a scalar-valued function; otherwise, P defines a vector-valued function.[31][32][33]
Definition

A function is primitive recursive iff it can be computed by a LOOP program.


This definition[31][19][34] agrees with the following:

A (vector-valued) function is primitive recursive if it can be written as
where each component is a (scalar-valued) primitive recursive function.

Loop nesting

edit

The central significance of the LOOP language lies in its stratification by maximum loop nesting depth. Writing Loopk(L) for the class of functions computable by programs in variant L with at most k-nested LOOP constructs, the four variants are related by the following inclusion diagram:

where an upward edge denotes that the upper variant has strictly more primitive instructions than the lower. L1 and L2 are incomparable: L1 has x = y but not dec x; L2 has dec x but not x = y; L3 has both; L0 has neither.

At each nesting depth k this induces strict inclusions along the edges:

with Loopk(L1) and Loopk(L2) incomparable for each k. The strict inclusions reflect the cost of simulating missing base instructions by extra loop nesting. In particular:

The reason is that dec (predecessor) is a depth-0 primitive in L2 and L3 but must be simulated by a loop in L1, costing one extra nesting level wherever it is used. Adding a base instruction never increases loop nesting depth; it can only lower it.[36] Predecessor is therefore the decisive primitive: without it, neither the Presburger functions nor the Kalmár elementary functions are reachable at the expected depth.

The example programs below track loop nesting depth explicitly for each variant.

Completing the set of basic instructions

edit

As all four systems compute the primitive recursive functions:

  • x = y is definable in L0 and in L2,
  • dec x is definable in L0 and in L1.

Bootstrapping x = y

edit

In L0 or L2 x = y can be implemented by the program

def copy(y) -> (x):
    LOOP y:
        inc x

We will write x = y in all languages, understanding that this instruction denotes x = copy(y) in L0 and in L2.
The overall nesting depths are[36]

""" Loop nesting: 1 (L0), 0 (L1) """

Wherever x = y appears in L0 / L2 code, one has to add to the local loop depth.

Bootstrapping dec x

edit

The predecessor function[37]

can be implemented by the programs

    in L0

def dec_L0(x) -> (x):
    LOOP x:
        x = 0
        LOOP a:
            inc x
        inc a

    in L1[38][39]

def dec_L1(x) -> (x):
    LOOP x:
        x = a
        inc a

We will write dec x in all languages, understanding that this instruction denotes dec_L0(x) in L0 and dec_L1(x) in L1.
The overall nesting depths are[36]

""" Loop nesting: 2 (L0), 1 (L1), 0 (L2) """

Wherever dec x appears in L0 / L1 code, one has to add respectively to the local loop depth.

Macros as syntactic sugar

edit

Let P and Q be LOOP programs.

Assume that Q is a program with input registers and output registers, computing a primitive recursive function

.

Suppose input registers of P supply the inputs , and we wish to use Q as a subroutine inside P.

Then the code of Q can be embedded ("macro-expanded") into P as follows:

  1. rename all registers of Q to avoid collisions with registers of P;
  2. copy the input values from P into the input registers of Q;
  3. initialize all auxiliary registers of Q to ;[40]
  4. execute the program Q;
  5. copy the output values from the output registers of Q into the designated registers of P.

This construction allows Q to be used as a subprogram of P, simulating an activation record in the sense required here.

Notes
  • In P only the output registers are updated.
  • This macro expansion does not alter the LOOP language. The notation is meant to improve the readability of the software and to facilitate the separate discussion of the inserted code. Machtey (1972) goes further. His function calls enhance the language to augmented loop programs. Machtey allows calls to general recursive functions.

Example programs

edit

Monus

edit

This is the monus function (cutoff-subtraction).

def monus(x, y) -> (x):
    """ Loop nesting: 3 (L0), 2 (L1), 1 (L2) """
    LOOP y:
        dec x     # add to local loop depth: 2 (L0), 1 (L1)

The expanded L1 code is listed by Meyer & Ritchie[41] and by Matos:[21]

def monus(x, y) -> (x):
    LOOP y:
        a = 0
        LOOP x:
            x = a
            inc a

Kronecker delta, if-then-else

edit

The equality of two variables and is tested with the Kronecker delta

def eq(x, y) -> (result):
    """ Loop nesting: 3 (L0), 2 (L1), 1 (L2) """
    result = 1  # assume equal

    test = monus(x, y)  # add to local loop depth: 3 (L0), 2 (L1), 1 (L2)
    LOOP test:
        result = 0      # x > y

    test = monus(y, x)  # add to local loop depth: 3 (L0), 2 (L1), 1 (L2)
    LOOP test:
        result = 0      # y > x

A conditional statement

IF x = y THEN execute P1 ELSE execute P2

can be translated to

test = eq(x, y)        # Loop nesting: 3 (L0), 2 (L1), 1 (L2)
LOOP test:
    P1
test = monus(1, test)  # Loop nesting: 3 (L0), 2 (L1), 1 (L2)
LOOP test:
    P2
Note
  • The auxiliary register test must not appear in P1 or P2. This is guaranteed in macro-expanded code by renaming registers to avoid collisions.

x modulo y

edit

This is the (integer) remainder

def mod(x, y) -> (x):
    """ Loop nesting: 4 (L0), 3 (L1), 2 (L2) """
    LOOP x:
        b = y           # add to local loop depth: 1 (L0), - (L1), 1 (L2)
        LOOP x: dec b   # add to local loop depth: 2 (L0), 1 (L1)
        z = y           # add to local loop depth: 1 (L0), - (L1), 1 (L2)
        LOOP b: z = 0
        LOOP z: dec x   # add to local loop depth: 2 (L0), 1 (L1)

exp2

edit

Define the function .

def exp2(x) -> (y):
    """ Loop nesting: 2 (L0) """
    inc y
    LOOP x:
        LOOP y:
           inc y
Notes
  • See also Meyer & Ritchie (1967a).[42]
  • Note that grows exponentially, but is computable by a LOOP2 program.

Addition

edit

Addition is the hyperoperation .

can be implemented by the following LOOP program:

def add(x, y) -> (x):
    """ Loop nesting: 1 (L0) """
    LOOP y:
        inc x

Multiplication

edit

Multiplication is the hyperoperation .

can be computed by the following LOOP program:[43]

def mult(x, y) -> (z):
    """ Loop nesting: 2 (L0) """
    LOOP x:
        z = add(z, y)

More hyperoperators

edit

Given a LOOP program for a hyperoperation , one can construct a LOOP program for the next level.

for instance (exponentiation)[44]

can be implemented by the following LOOP program:[43]

def power(x, y) -> (z):
    """ Loop nesting: 3 (L0) """
    inc z
    LOOP y:
        z = mult(z, x)

This program expands to

def power(x, y) -> (z):
    z = 1    # shorthand for z = 0; inc z
    LOOP y:
        temp = 0
        LOOP x:
            LOOP z:
                inc temp
        z = temp
Note
def power(x, y) -> (z):
    """ Loop nesting: 4 (L0), 3 (L1), 3 (L2), 2 (L3) """
    T = exp2(exp2(add(x, y)))
    LOOP T:
        if p == 0             : z = 1; yi = y;    p = 1
        if p == 1 and yi == 0 :                   p = 8 # final state
        if p == 1 and yi != 0 : temp = 0; xi = x; p = 3
        if p == 2             : dec yi;           p = 1
        if p == 3 and xi == 0 : z = temp;         p = 2
        if p == 3 and xi != 0 : zi = z;           p = 5
        if p == 4             : dec xi;           p = 3
        if p == 5 and zi == 0 :                   p = 4
        if p == 5 and zi != 0 :                   p = 7
        if p == 6             : dec zi;           p = 5
        if p == 7             : inc temp;         p = 6

The computation of T an upper time bound sufficient to complete the simulation is Loop2.

The if lines are Loop1 in L3:[45]

    ...
    test = dec(add(eq(p, 3), eq(xi, 0)))             # line 8
    LOOP test:
        z = temp     # add to local loop depth: 1 (L0), 1 (L2)
        p = 2
    ...
    test = dec(add(eq(p, 5), monus(1, eq(zi, 0))))   # line 12
    LOOP test:
        p = 7
    ...
Note
  • The loop nesting can be reduced to 2 even in L2, see below

The steepest functions

edit

Since the initial functions are all dominated by the successor function, iterations of it dominate all functions defined by iterations from the initial functions.[46] Let be the (finite-level) fast-growing hierarchy of functions

where is the n-th iterate of .[47]

For fixed , can be computed by a LOOP program of nesting depth :

""" Loop nesting: k (L0) """
def F_k(n) -> (n):
    LOOP n:                  # nesting depth: 1
        LOOP n:              # nesting depth: 2
            ...              # ...
                LOOP n:      # nesting depth: k
                    inc n    #
Note
  • The diagonal function grows faster than for every fixed , and is a version of the Ackermann function. It is not LOOP-computable: any LOOP program has a fixed, finite nesting depth , so it can only compute functions dominated by for that particular .

Presburger functions

edit

Consider programs with loop nesting depth at most 1. Then the class of functions computable by L2 programs is the class of Presburger functions.[26]

Kalmár elementary functions

edit

Consider programs with loop nesting depth at most 2. Then the class of functions computable by L2 programs is the class of Kalmár-elementary functions. In particular, Loop2(L2) contains .[35]

Example

The identity

[35]

allows to be computed by superposition from the substitution basis ,[48]
yielding a Loop2 program in L2 or L3:

def power_by_superposition(x, y) -> (result):
    """ Loop nesting: 4 (L0), 3 (L1), 2 (L2) """
    base = mult(x, y)               # will become xy+x+1
    base = add(base, x)
    inc base                        # base = xy+x+1
    e = mult(base, y)               # e = (xy+x+1)y
    dividend = exp2(e)
    divisor = exp2(base)
    divisor = monus(divisor, x)     # Loop nesting: 3 (L0), 2 (L1), 1 (L2)
    result = mod(dividend, divisor) # Loop nesting: 4 (L0), 3 (L1), 2 (L2)
Note
  • The availability of dec x as a basic instruction is crucial for ensuring that the (Kalmár elementary) function remains computable within Loop2. Without predecessor, the computation of requires nesting depth greater than 2 (4 in L0, 3 in L1). Allowing depth 3 would be overly powerful, since it enables computation of the non-elementary fast-growing function (see above). Consequently, the Kalmár elementary class cannot be characterised by any fixed nesting depth in L1: the hierarchy jumps from strictly below at depth 2 to strictly above at depth 3, bypassing entirely. Adding predecessor as a primitive restores the level-by-level correspondence throughout the hierarchy.[49][26]

See also

edit

Notes

edit
  1. Enderton 2012.
  2. Cutland 1980, p. 9.
  3. 1 2 Changes of the content of register x during the execution of the loop do not affect the number of passes.
  4. Schöning 2008, p. 93.
  5. Schöning 2001, p. 122.
  6. Schöning 2008, p. 112.
  7. 1 2 Meyer & Ritchie 1967a.
  8. Brock 2020.
  9. Ritchie 1967.
  10. Ritchie 1967a.
  11. Schöning 2008, p. 105.
  12. Minsky 1967, pp. 212–213, section 11.7.
  13. Tsichritzis 1970, p. 729, Definition 1.
  14. Machtey 1972.
  15. 1 2 Beck 1975.
  16. Fachini & Maggiolo-Schettini 1979, pp. 59–60.
  17. Goetze & Nehrlich 1980.
  18. Calude 1988, p. 384.
  19. 1 2 Schöning & Pruim 1998, p. 25.
  20. Tourlakis 2012, p. 126, 2.2.0.8.
  21. 1 2 Matos 2015, p. 68.
  22. Tsichritzis 1971.
  23. Kfoury, Moll & Arbib 1982.
  24. Odifreddi 1989, p. 70, 68.
  25. Matos 2014.
  26. 1 2 3 4 Cherniavsky 1976.
  27. Cherniavsky & Kamin 1979, p. 121.
  28. To L3 Handley & Wainer 1999, p. 278 added as basic the conditional if x = y then ... else ... fi.
  29. Goetze & Nehrlich 1980, p. 261.
  30. We write .
  31. 1 2 Fachini & Maggiolo-Schettini 1979.
  32. Matos 2015.
  33. PlanetMath.
  34. Handley & Wainer 1999, pp. 278–279.
  35. 1 2 3 Prunescu, Sauras-Altuzarra & Shunia 2025.
  36. 1 2 3 In the docstrings below, only depths that improve upon all lower variants in the diagram are listed; omitted variants inherit the minimum depth of their predecessors.
  37. Implementation in Python:
    x = 0 if x == 0 else x - 1
    
  38. Matos 2015, p. 68, Example 2.
  39. Tourlakis 2022, p. 157.
  40. to remove nonzero values left by a previous pass.
  41. Meyer & Ritchie 1967a, p. 466, (2.2)..
  42. Meyer & Ritchie 1967a, p. 467-468:
    def exp2(x) -> (y):
        inc y
        LOOP x:
            z = 0
            LOOP y:
                inc z
                inc z
            y = z
    
  43. 1 2 The expansion of assignments is explained above.
  44. For more details, see Powers of zero and/or Zero to the power of zero.
  45. Schöning & Pruim 1998, p. 28.
  46. Odifreddi 1999, p. 298.
  47. Prunescu, Sauras-Altuzarra & Shunia (2025) derived and from the basis . Here these functions are added to the basis to simplify the computation.
  48. Tsichritzis 1970.

References

edit
  • Beck, H. (1975). "Zur Entscheidbarkeit der funktionalen Äquivalenz". In Brakhage, H. (ed.). Automata Theory and Formal Languages. Lecture Notes in Computer Science (in German). Vol. 33. Berlin, Heidelberg: Springer. pp. 127–133. doi:10.1007/3-540-07407-4_16.
  • Odifreddi, Piergiorgio (1989). Classical Recursion Theory: The Theory of Functions and Sets of Natural Numbers. Vol. I. Studies in Logic and the Foundations of Mathematics. Vol. 125. North-Holland. pp. xvii + 668. ISBN 0444872957.
  • Prunescu, Mihai; Sauras-Altuzarra, Lorenzo; Shunia, Joseph M. (24 May 2025). "A Minimal Substitution Basis for the Kalmar Elementary Functions". arXiv:2505.23787 [math.LO].
  • Tourlakis, George J. (2012). Theory of Computation. Hoboken, New Jersey: John Wiley & Sons, Inc. ISBN 978-1-118-01478-3.

Bibliography

edit
  • Fachini, Emanuela; Maggiolo-Schettini, Andrea (1982). "Comparing Hierarchies of Primitive Recursive Sequence Functions". Zeitschrift für mathematische Logik und Grundlagen der Mathematik. 28 (27–32): 431–445. doi:10.1002/malq.19820282705.
edit