Block Lambdas: break and continue

Allen Wirfs-Brock allen at wirfs-brock.com
Sun Jan 15 14:31:06 PST 2012


On Jan 15, 2012, at 11:56 AM, Brendan Eich wrote:

>> 	Grant Husbands	January 15, 2012 8:00 AM
>> 
>> The above would desugar to:
>> label1: arr.forEach { |o|
>>   if (...) break label1;
>> }
>> 
>> If it had continue instead of break, it would desugar to:
>> arr.forEach { |o|
>>   label2: {
>>     if (...) break label2;
>>   }
>> }
>> 
>> It wouldn't need to be described as a desugaring in a strawman, given
>> the similarity to typical break/continue handling, but I think that
>> makes the meaning clear.
> 
> No, this is a special form and if it can be specified by desugaring, that is better in principle than extending the spec's kernel semantics. Whether it actually pays to gensym a label and translate in the spec is TBD.
> 
> Thanks for writing this down. You're right, I was wrong: a new
> 
>> for arr.forEach { |o|
>>   if (...) break;
>> }
> 
> special form would not require exception-reified break and continue given the desugaring you show.

I really like this because it lexically connects the loop label (explicit or implicit) with those referenced by break/continue within the associated block lambda. This eliminates all the dynamic scoping issues we had been grappling with on this thread.


> 
> Let's talk about syntax, though. This use of for makes something that looks like a paren-free for loop, but there's no body. Note that (as Francois pointed out earlier) block-lambdas cannot start statements, and the block-lambda above is an argument to arr.forEach.
> 
> (The "for" repeating after the keyword in the "forEach" name is unfortunate, but let's say we can migrate to a JQuery-like "each" name to resolve this glitch.)
> 
While "for" may or may not be the best keyword, but you seem to be reading additional implications into it here and below in the context of arr.forEach.  This is a general constructor for labeling exit points from calls that take literal block lambdas as arguments.  The "forEach" is just one of an indefinite number of function names that might be called with such a construct.  Some of those, may have names that clash with the initial keyword, what it is. 


> Even with paren-free moving toward more significant newlines, the newline in the example does not terminate the loop "head", it is part of the block-lambda in the head. So this is future-hostile to paren-free.

I assume that the syntactic production we are de-sugaring is:
   IterationStatement :: for [no  ( here] expression ;

The [no ( here] is to disambiguate from other for forms.  If we used a new contextual keyword it would be 
   IterationStatement :: EGloop [no  LineTerminator here] expression ;

I don't see in either case, why you would have any paren-free problems as it is essentially just an expression statement with a prefix keyword. In particular, there is no "loop head" and no "body". that is just an interpretation of the arguments for some specific use cases.  In fact, the functional abstraction may not be a loop at all. 

Also, things like the following
   for firstCall({|| ... break ...}).secondCall({||...break...}).thirdCall {||...break...};

are possible.  In these cases, the first and second breaks may be a bit unintuitive as they break out of the statement rather than the call that contains them. 
   
> 
> I wonder whether we need this use of "for". It's not clear the continue case arises enough with forEach to be worth it. The break case is already satisfied by some/every. If we can defer this sugar until it's clear we know the need for it is strong enough to measure, we ought to -- esp. given the paren-free conflict.

I don't understand the conflict, but I agree we could do block lambdas while deferring extended break/continue semantics .  But this does seem like a neat approach.

> 
> We could try for other syntax, but it too would have the body-less problem. Unless we use a new keyword:
> 
>> loop arr.forEach { |o|
>>   if (...) break;
>> }
> 

Even though these are not necessarily "loops", I think loop is still a plausible keyword.  Or consider in as a prefix operator:

in arr.forEach { |o|
     if (...) break;
}

or

here: in arr.forEach { |o|
     elsewhere: while (true) {
      if (...) break here; else break elsewhere;
}

If you interpreted in as a prefix operator, then you could say:

in (in (in firstCall({|| ... break ...})).secondCall({||...break...})).thirdCall {||...break...};

if you needed to. Note the outer in (the expression statement) could be explicitly labelled but the inner ins could not.


> We could contextually reserve loop. But statements are part of JS, and so I suspect people would want this to do what Doug Crockford has suggested it to: indefinitely iterate its body (here the arr.forEach call expression statement, which would have a semicolon inserted automatically after). This is a conflict, since forEach does its own looping.

I suspect that learning that loop does do what Doug suggested is less of a burden than leaning what {||...} means.

> 
> Other ideas?

label: do (arr.forEach {|o|
   ...
   if (...) break label;
});

hate the extra ( ) needed to disambiguate other do usages.

label: break {arr.forEach {|o|
   ...
   if (...) break label;
}};

extra { } not much better

label: with arr.forEach {|o|     //no ( here after the with keyword
   ...
   if (...) break label;
};

repurposed with keyword, scopes the label

Allen
    
> 
> /be
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120115/9e6e9006/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: compose-unknown-contact.jpg
Type: image/jpeg
Size: 770 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120115/9e6e9006/attachment.jpg>


More information about the es-discuss mailing list