For Loop Desugaring (was: return when desugaring to closures)

David-Sarah Hopwood david.hopwood at industrial-designers.co.uk
Tue Oct 14 07:17:28 PDT 2008


Brendan Eich wrote:
> On Oct 13, 2008, at 7:48 PM, Mark S. Miller wrote:
> 
>> On Mon, Oct 13, 2008 at 5:00 PM, Jon Zeppieri <jaz at bu.edu> wrote:
>>> 1) for (var i = 0; i < len; i++) ... continues to mean what it  
>>> means in ES3.
>>> 2) for (let i = 0; i < len; i++) ... has the proper scope for 'i'
>>> (which you reiterated above), *and* 'i' is rebound -- not mutated --
>>> on each iteration.
>>> 3) The rewrite rules are the *same,* regardless of whether it's a  
>>> "for
>>> (var ...)" or a "for (let ...)" loop.
>>>
>>> At least, that's what I took Mark to mean.  He can correct me if  
>>> I'm wrong.
>>
>> You're right. However, the desugaring is more complex than I expected.
>> Thanks for asking me to write it down.
>>
>>  for (<keyword> <varName> = <initExpr>; <testExpr>; <updateExpr>)  
>> { <body> }
>>
>> desugars to (hygienic  renaming aside):
>>
>> breakTarget: {
>>  const loop = lambda(iter = <initExpr>) {
>>    <keyword> <varName> = iter;
>>    if (! <testExpr>) { break breakTarget; }
>>    continueTarget: { <body> }
>>    lambda(iter2 = <varName>) {
>>      <keyword> <varName> = iter2;
>>      <updateExpr>;
>>      loop(<varName>);
>>    }();
>>  };
>>  loop();
>> }

I think this is equivalent to my third attempt in
<http://www.mail-archive.com/es-discuss@mozilla.org/msg00947.html>,
except that the latter was not attempting to handle break/continue.
To do that correctly, it would be:

    label: for (let i = initExpr; condExpr; updateExpr) { body }
  ==>
    {
      const $loop = lambda(i) {
        (updateExpr);
        if (condExpr) {
          const ($continue$label = lambda {}) { body }
          $loop(i);
        }
      };
      let i = initExpr;
      if (condExpr) {
        const ($continue$label = lambda {}) { body }
        $loop(i);
      }
    }

['continue label;' or 'continue;' within 'body' expands to
'$continue$label()', assuming that escape continuations can be called.
'break label;' or 'break;' within 'body' expands to itself.
An unlabelled 'for' is equivalent to one with a fresh label.]

> Requirement 3 is met because var hoists to the enclosing function.  

Assuming that var hoisting is done before this expansion, yes.

[...]
>> However, in contradiction
>> to my original claim, one couldn't usefully say "const" instead of
>> "let" with a for(;;) loop.
> 
> Why not usefully? It's true the consts do not hoist to leave an effect  
> in the variable object, but they do ensure const-ness.

The problem is the update expression -- it must be able to mutate a
variable that is distinct from any of the variables captured by the
body in any iteration. That's possible, but requires a different expansion.
Something like:

    label: for (const i = initExpr; condExpr; updateExpr) { body }
  ==>
    {
      let i = initExpr;
      const $loop = lambda {
        (updateExpr);
        const (i = i) {
          if (condExpr) {
            const ($continue$label = lambda {}) { body }
            $loop();
          }
        }
      };
      const (i = i) {
        if (condExpr) {
          const ($continue$label = lambda {}) { body }
          $loop();
        }
      }
    }

(I'm assuming that 'const (i = i) {...}' is allowed and that the
i on the RHS refers to the one in the surrounding scope. If this is
not allowed, then use 'const ($t = i) {const (i = $t) {...}}'.)

This is subject to the criticism that the loop variable(s) are
implicitly mutable in the update expression (only), when they were
declared it to be const.

No such criticism would apply to 'for each (const ...', though.

-- 
David-Sarah Hopwood


More information about the Es-discuss mailing list