Next: , Previous: Types and Declarations, Up: Top

5 Problems with Code Movement

Some iterate clauses, or parts of clauses, result in code being moved from the location of the clause to other parts of the loop. Drivers behave this way, as do code-placement clauses like initially and finally. When using these clauses, there is a danger of writing an expression that makes sense in its apparent location but will be invalid or have a different meaning in another location. For example:

  (iter (for i from 1 to 10)
        (let ((x 3))
          (initially (setq x 4))))

While it may appear that the x of (initially (setq x 4)) is the same as the x of (let ((x 3)) ..., in fact they are not: initially moves its code outside the loop body, so x would refer to a global variable. Here is another example of the same problem:

  (iter (for i from 1 to 10)
        (let ((x 3))
          (collect i into x)))

If this code were executed, collect would create a binding for its x at the top level of the iterate form that the let will shadow.

Happily, iterate is smart enough to catch these errors; it walks all problematical code to ensure that free variables are not bound inside the loop body, and checks all variables it binds for the same problem.

However, some errors cannot be caught:

  (iter (with x = 3)
        (for el in list)
        (setq x 1)
        (reducing el by #'+ initial-value x))

reducing moves its initial-value argument to the initialization part of the loop in order to produce more efficient code. Since iterate does not perform data-flow analysis, it cannot determine that x is changed inside the loop; all it can establish is that x is not bound internally. Hence this code will not signal an error and will use 3 as the initial value of the reduction.

The following list summarizes all cases that are subject to these code motion and variable-shadowing problems.