chickadee » foof-loop » LOOP

(LOOP [<loop-name>] (<loop-clause> ...) [=> <final-expression>] [<body>])syntax

The LOOP form follows the general syntax:

 (LOOP [<loop-name>] (<loop-clause> ...)
   [=> <final-expression>]
   [<body>])

<Loop-clause> may have one of the following forms:

 (<loop-variable> <initializer> [<update>])
 (WITH <loop-variable> <initializer> [<update>])
 (FOR <variable> ... (<iterator> <argument> ...))
 (LET <variable> <expression>)
 (WHILE <condition>)
 (UNTIL <condition>)

A loop steps through each iterator in parallel, repeatedly evaluating the body, until the termination condition of any loop clause is true. Then if the <final-expression> is supplied, it is evaluated in an environment with the final values of all the loop variables bound, as well as any final bindings supplied by the iterators; if there is no <final-expression>, the value of the invocation of the loop is unspecified.

If <loop-name> is not supplied, the loop continues at the end of the loop body, which may be empty. If <loop-name> is supplied, it is bound to a macro that must be called in the loop body to continue the loop. This macro may be called arbitrarily many times, in tail or non-tail positions; it is appropriate for recursive loops. (Note, though, that some iterators are non-reentrant, which may make multiple calls unsafe. These iterators are discouraged, however, and by convention are marked with a suffix of !; e.g., LISTING! and LISTING-INTO!.)

The macro <loop-name> accepts arguments by which to update the loop variables. Arguments may be passed by position, or by name, or both. Positional arguments come first, corresponding with the loop variables in leading WITH clauses. After positional arguments are updates to named variables, written (=> <variable> <update>). Loop variables from iterators may be updated only by name.

All arguments to <loop-name> are optional: all loop variables have default update expressions. A loop variable from (WITH <variable> <initializer> [<update>]) by default either is updated to the value of <update>, or remains unchanged if no <update> was supplied. Loop variables from iterators by default are updated according to the iterator.

 (loop continue ((with a 0)
                 (with b '()
                   ;; Default update for B:
                   (cons a b))
                 (for c d (in-list '(i j k p q r))))
   (write (list a b c d))
   (newline)
   (continue (+ a 1)    ;First position, for the first (WITH A 0).
             (=> d (cddr d))))   ;D is from (FOR C D (IN-LIST ...)).
 ;; Output:
 ;(0 () i (i j k p q r))
 ;(1 (0) k (k p q r))
 ;(2 (1 0) q (q r))

Many variables are involved in a loop, some written by the user and some which arise only from iterators. Each variable falls into one of several classes:

  • Loop variables are initialized at the beginning of the loop and updated at every iteration by rebinding (not by assignment). Loop variables are bound in the user termination conditions, the user variable expressions, the loop body, and the final expression.
  • Entry variables are bound when each iteration of the loop is entered, before the termination conditions have been tested. They cannot be updated like loop variables; each is always initialized on loop entry to the value of the same expression. This means that the expression need not be duplicated at the beginning of the loop and when the loop is continued. Entry variables arise only from iterators, and are bound in the user termination conditions, the user variable expressions, the loop body, and the final expression.
  • Body variables are bound at each iteration of the loop only if no iterator has terminated the loop. Body variables arise only from iterators, and are bound in the user termination conditions, the user variable expressions, and the loop body.
  • User variables are bound after body variables have been bound, but before WHILE and UNTIL conditions for terminating the loop. User variables are introduced by LET and LET-VALUES clauses in LOOP. User variables are bound only in the user termination conditions and the loop body.
  • Final variables are bound once at the end of the loop. Final variables arise only from iterators, and are bound only in the final expression.

The loop is controlled by the loop clauses:

 (WITH <variable> <initializer> [<update>])

Introduces a loop variable named <variable>, initialized to the value of <initializer>. On each iteration of the loop, the variable may be explicitly updated; if it is not explicitly updated, but <update> was supplied, then the variable is updated to the value of that expression in an environment with all loop variables, entry variables, body variables, and user variables bound; otherwise, <variable> remains unchanged at the next iteration of the loop.

 (<variable> <initializer> [<update>])

Identical to (WITH <variable> <initializer> [<update>]). This form is provided to make the LOOP syntax compatible with named LET, and very similar to DO:

     (let next ((x 0))
       (if (< x 10)
           (begin (write x) (next (+ x 1))))),
     
     (loop next ((x 0))
       (if (< x 10)
           (begin (write x) (next (+ x 1))))),
     
     (do ((x 0 (+ x 1)))
         ((>= x 10))
       (write x)), and
     
     (loop ((x 0 (+ x 1))
            (until (>= x 10)))
       (write x))

are all equivalent.

Prefer the WITH syntax; it is visually distinct and easier for pretty-printers to indent better with less effort.

 (FOR <variable> ... (<iterator> <argument> ...))

General iteration form. <Iterator> can do as it pleases with the <variable>s and <argument>s, but by convention the <variable>s are variables to be bound in the loop body or the final expression, and the <argument>s are expressions or directives controlling the iteration.

The FOR clauses have arbitrary order; relying on it is an error.

 (LET <variable> <expression>)
 (LET-VALUES <bvl> <expression>)

LET binds <variable> to the value of <expression> inside the body bindings, and before the user termination conditions and loop body. LET-VALUES binds all the variables in the bound variable list <bvl> to the respective values returned by <expression>. The clause (LET <variable> <expression>) is equivalent to the clause (LET-VALUES (<variable>) <expression>).

 (WHILE <condition>)
 (UNTIL <condition>)

After no iterator has terminated the loop, <condition> is evaluated in an environment with all of loop variables, entry variables, body variables, and user variables bound. If true, the iteration continues for WHILE and terminates for UNTIL, and vice versa.