Wandering Thoughts archives


Arranging scopes for for loops

Suppose that you have a language where for loop index variables are local to the for loop; they aren't visible outside it. What I'll call the 'for loop closure problem' is that in a straightforward implementation of their lifetime, the following pseudo-code doesn't work the way people think it will:

nums := [];
for (var i = 0; i < 100; i++)
  nums.append(func(x) {i+x});

In straightforward scope and variable lifetime semantics, every lambda returns the same result because each lambda captured the same i (and after the loop finishes, the value of i is 100). When I was discussing what Python closures close over, I mentioned that you could fix this with careful use of how scopes are defined.

First, let's rewrite this for loop to show the scope explicitly:

{var i;
 for (i = 0; i < 100; i++)
   nums.append(func(x) {i+x});

If we want each iteration of the loop (and thus each lambda) to get a different version of i, we need to explicitly create a scope for each of them. However, we need all of the following to be true:

  • the for loop condition has a single sustained scope, in which its version of i is the same over all iterations of the loop. This creates an outer version of i.
  • the for loop body has a per-iteration scope which contains an inner version of i.
  • the inner version of i is initialized from the outer version of i.

Of these the only tricky requirement is the third one.

You could fix this with specific rules for scopes in for loops, but as it happens we don't have to bother with this; the language already has a way of expressing this outcome within the existing scoping rules. We just say that the loop body acts as if it was an anonymous function (well, closure) that is invoked on each loop iteration. This effectively rewrites the loop as:

{var i;
 for (i = 0; i < 100; i++)
   func(i) {
     nums.append(func(x) {i+x});

Anonymous functions already create new scopes when invoked and function calls are the general method for passing variables into a scope, so we're done.

(Various sorts of optimizations can and generally are applied in languages that define for loops this way, because you really don't want to be creating a call frame for every loop iteration.)

I believe that doing this sort of conceptual rewriting is especially popular in languages (like many versions of Lisp) that consider lambdas and closures to be fundamental building blocks of the language. In these languages, rewriting loops as invocation of anonymous functions makes a lot of sense even if you don't care about the scoping rules. The extreme version is to not even have loop iteration as such in the language's core; instead you transform loops into recursive function calls. (Full tail call optimization is required for obvious reasons.)

programming/ScopingForLoops written at 00:30:35; Add Comment

Page tools: See As Normal.
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.