On
Jan 15, 2012, at 11:56 AM, Brendan Eich wrote:
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.
Yeah, but why have it at all if users of functions such as forEach
(canonical example, not only case) used with block-lambdas do not need
break or continue much (or at all)?
(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.
No, come on -- I mean, seriously! I tried to make it as plain as
possible by writing this parenthetical aside that this comment about
forEach stuttering the proposed 'for' keyword prefix is not a big deal.
It's not nothing either.
Yes, there are other use-case names than forEach. No, I do not agree
that 'for' is the obvious best keyword prefix, for the reasons I gave:
it's future-hostile and it does not define a loop with head and body.
This is a general constructor for labeling exit points
from calls that take literal block lambdas as arguments.
Right, but if break and continue from "loops" such as arr.forEach are
rare enough, why not use a real label for labeling the exit point?
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.
Whatever the method name, if you do not commit to a keyword, then the
programmer can pick a *label* that does not stutter.
This is all a sideshow, and the parentheses I used should indicate. The
big issues remain future-hostility and body-less head (or is it
head-less body?).
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".
I think you are forgetting the for(;;) loop. Paren-ful for loop:
for (x {||}; y; z) {
w;
}
Paren-free:
for x {||}; y; z {
w;
}
(as in Go, btw).
Proposed for-prefix followed by headless body:
for x {||}; y; z {
z;
}
A syntax error if the left brace is as shown. But either move the { to
the next line, or get rid of bracing and use significant newline to
terminate the head, and we have:
for x {||}; y; z
{
w;
}
With the for-prefix proposed first by Axel, this is either (for x {||});
y; z; { w; } or for (x {||}; y; z) { w; }. There are two ways to parse a
valid sentence. The grammar is ambiguous.
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.
Skipping the prefix magic and making the user, for this exceedingly rare
hard case, use an explicit label, seems much better. Then there is no
"may be a bit unintuitive" issue (which could be an understatement, if
this case were not so rare that we cared!).
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,
See above.
but I agree we could do block lambdas while deferring
extended break/continue semantics . But this does seem like a neat
approach.
I'm not slamming the door, but I do not want to lumber block-lambdas
with more complexity if the hard case is as rare as I contend. Anyway
"neat" is not enough. We need an unambiguous prefix syntax.
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.
"do" is even better, but obviously ambiguous with do-while loops. If you
are going to write
do arr.forEach { |o|
if (...) break;
...
} while (0);
then you might as well just use a label:
L: arr.forEach { |o|
if (...) break L;
...
}
Or consider in as a prefix operator:
in arr.forEach { |o| if (...) break; }
Horribly ambiguous, ES3 and up do not forbid line terminator to left of
'in' operator.
or
here: in
arr.forEach { |o| elsewhere: while (true) {
if (...) break here; else break elsewhere; }
The labels mean you don't need "in" at all:
here: arr.forEach { |o| elsewhere:
while (true) { if (...) break here; else break
elsewhere; }
What was the "in" for again? No "continue" usage here, which is more
awkwardly translated to a break from inner labeled block as Grant
showed. But continue is an even rarer hard case.
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.
Did you mean "does not"?
"loop" is the wrong word if there is no loop, which you point out is
possible -- but not plausible if we are discussing break and
(especially) continue. I think we'd be nuts to use "loop" other than as
Doug showed in that talk, but then I'd rather stick with for(;;) or
while(true) for an iloop -- and so would most developers, since they
have to do it today and it's not a big hardship.
Other ideas?
label: do
(arr.forEach {|o| ... if (...) break label; });
hate
the extra ( ) needed to disambiguate other do usages.
I do! :-P
label:
break {arr.forEach {|o| ... if (...) break
label; }};
extra { } not much better
I repeat if you are willing to use a label, you don't need a prefix
keyword. See Grant's desugarings.
/be
|