Should I avoid resetting "by hand" an auto-incremented loop variable?

Another possibility:

(loop for z = 0 then (if ... 0 (1+ z))
      ...
      )

I think you should avoid doing so, for two reasons.

Firstly as far as I can see the loop specification makes no clear statement about whether this is safe, but dotimes for instance says that

It is implementation-dependent whether dotimes establishes a new binding of var on each iteration or whether it establishes a binding for var once at the beginning and then assigns it on any subsequent iterations.

In other words

(dotimes (i n)
  ... my code ...)

might expand to something like

(let (...)
  ...
  (tagbody
   start
   (let ((i ...))
     ...my code...)
   (unless ... (go start))
   ...))

for instance.

I suspect pretty strongly that loop implementations don't do this in practice, and I think you probably could argue that the spec implies they are not allowed to do it, but I also think that the wording is not clear enough that you'd want to depend on that.

Secondly I think that as a matter of style adjusting the iteration variable's value inside the body of the iteration is, well, horrible: If someone reads (loop for i from 0 below 100 do ...) they expect i to step by integers from 0 to 99, not to jump about randomly because of some devious obscurity in the code. So I would personally avoid this as a matter of style rather than as a matter of being sure the spec allows or does not say it is safe.

Rather I would do what Rainer suggested, which is (loop for x = ... then ...) where it is clear that x has values which are determined by your code, not by loop.

Finally note that you can detect the iteration-variable-bound-on-each-iteration thing by, for instance:

> (mapcar #'funcall (loop for i below 3
                                    collect (lambda () i)))
(3 3 3)

In this case the implementation I am using does not, in this case rebind it (but it might do in other cases of course!).


It's also worth noting that the possibility of assignment to loop control variables prevents an important optimisation being possible: loop unrolling. If the system can't assume that (loop for i from 0 to 2 do ...) evaluates ... three times, with i being the appropriate value, you can't really unroll such a loop.


ANSI CL contains multiple instances of wording which have the clear interpretation that variables are bound once and stepped destructively.

As far as mutating the variables of "variable initialization and stepping clauses goes", this is something that conforming code can do. The ANSL CL standard makes it clear that variables are bound once and then stepped by assignment, and that clauses are processed in order, with only certain exceptions.

In particular, there is this text in 6.1.2.1 Iteration Control:

The for and as clauses iterate by using one or more local loop variables that are initialized to some value and that can be modified or stepped after each iteration. For these clauses, iteration terminates when a local variable reaches some supplied value or when some other loop clause terminates iteration. At each iteration, variables can be stepped by an increment or a decrement or can be assigned a new value by the evaluation of a form.

See the last bit: "can be assigned a new value by the evaluation of a form".

Your alternative code using with is a bit verbose on two counts. Incrementing a variable can be done with incf. Secondly, the do clause of loop takes multiple forms; no need for progn.

Thus, if we needed this alternative, we could at least have it like this:

(loop
    with z = 0
    ...
    do (incf z)
       ...
       (when ...
          (setq z 0)))

Or possibly with a when clause:

(loop
    with z = 0
    ...
    do (incf z)
       ...
    when (condition ...) do
      (setq z 0))