Block Lambdas: break and continue
Herby Vojčík
herby at mailbox.sk
Sun Jan 15 02:29:20 PST 2012
Wait, wait! As an intuitive kind of person I often come to results but don’t
exactly know the logical path, which I must painfully reconstruct later.
That said, I have to strongly oppose this:
===
Anyway, as others have written, this seems an abuse of 'continue'.
===
On the contrary, I would say. I will explain below. It is related to the
next cited paragraph.
===
Also, you cannot avoid the exception-like effect if this is to propagate to
the forEach's internal loop. There's no loop visible to the spec or an
implementation's compiler. So this really is a throwing form. Again I do not
see how it can be "exception-less".
===
My view on continue was "stop evaluating the block and continue with the
control-flow". This is purely control-flow friendly, I would even say
control-flow-submissive notion of continue. I see no need for any exceptions
there.
So, I would like to elaborate here a in more detail about continue and
break. It shows my proposal for early return was not abuse of continue
keyword, and also lays down a possibility to reuse both continue and break
naturally without any need for exceptions.
For now, the semantics of "continue;" is explained this way:
Stop the processing of nearest enclosing loop body and continue the
control flow with the next iteration of the loop.
Now, my bold thesis is: This is not the true meaning of continue. The true
meaning of continue (as per the Occam razor) is:
Stop the processing of nearest enclosing continue-scope-block and
continue with the control flow.
You don't need the notion of iterator, you even don't need the notion of
loop. You only need the (weak and general) continue-scope-block for this to
work. Continue-scope-block is presently defined as "body of for statement;
or body of while statement; or body of do-while statement" (it is probably
only a coincidence they are all loops :-) ).
Give it a though, just for a while.
[Note: Even from "humanly" point of view, the latter seems more natural and
better to grasp; the former seems too artifical. Maybe it's only me.]
First thing to note: If you accept the latter definition of "continue;" (I
will cover labeled one, later), you did not break anything in the ES5. It
simply works.
Second thing to note: Since lambda-blocks are new to ES.next, you can define
behaviour that relates it as you please. By defining what continue and break
mean inside a lambda-block, you do not break any old code (since it did not
contain any lambda-block).
Now, let us do simple addition to definition of continue-scope-block by
adding "; or lambda-block" to it. Leave the rest unchanged.
Now "continue;" work (naturally) the same as for for body, while body and
do-while body - it stops completion of the lambda-block and lets the outer
control-flow go on. In other words, "continue".
This should complete first half of my defense against "you abuse continue".
I never meant to. I just grasped its true nature. The one of "skip to the
end of block and continue".
But the lambda-block now, getting promoted into continue-scope-blocks, may
raise a humble protest: "Well, for the other colleagues, the complation
value does not matter at all, so it is fine when continue gives them
undefined. But for me, it does. I am defined to return a value!".
FIne, "continue;" stops completion; it should have the possibility to
specify the completion value. For example
continue | expression |;
The bars differentate it from label and also give a hint that the value only
matters for the lambda-block (it does not harm in any way when used in
syntax-loop).
This should conclude the second half of my defense against continue abuse.
---
As for the labeled continue, the loop-less and iteration-less definition
works, too. You just change a few words to get:
Stop the processing of enclosing continue-scope-block whose respective
control structure is labeled by label and continue with the control flow.
Here, the respective control structure is for-loop for for-body, while-loop
for while-body, do-while-loop for do-while-body and function call that uses
it as parameter for lambda-block.
In a sense, labeled continue is weaker than non-labeled, since lambda-block
must be (lexically) present inside labeled call. If it is not, the continue
label statement should be an error.
And, of course
continue | expr | label;
is possible.
---
So the bottom line is: new continue is functionally equivalent to the old
continue (only its meaning is re-specified), and its works in lambda-block
with consistent meaning, exception-less.
---
As for the break, ... I can do very similar thing. But first thing one
should realize is, continue and break are _not_ close siblings. Because
break already works in switch. They _seem_ similar, but they are different.
The loop-less definition of labeled break is:
Stop the processing of respective control structure (labeled by label)
of enclosing break-scope-block, and go on with the control flow.
So the break, well, breaks out of control structure labeled by label, which
must be the respective control structure of break-scope-block. This
definition is a bit clumsy of the first look, but it is so that unlabeled
break can be similar:
Stop the processing of respective control structure of innermost
enclosing break-scope-block, and go on with the control flow.
See? You always break out of respective control structure of
break-scope-block, either innermost one, or one that has its control
structure lebeled.
Now, again, we include lambda-block in the pack along with for-body,
while-body, do-while-body and switch-body (because that is in
break-scope-block group, too).
And again, lambda-block gives a humble complaint that for the colleagues,
the completion value of the respective control structure does not matter,
but for lambda-block, it matters. It is a function call, after all, its
value may be used!
And again, we may give break the syntax
break | expr | [label];
to solve the problem.
---
Another bottom line is, this is again consistently (after re-thinking the
previous definition; break is changed less) working for previous code as
well as for lambda-blocks and is exception-less.
So I’d say there is no need for throw continue; and throw break;. You can
reuse continue and break - they are good enough for that, they are just shy
to show you their real nature :-)
---
Here I'd stress again the difference between unlabeled continue and the
rest. Unlabeled continue is most control-flow friendly construct; you can
say it is "relative". It works purely on local ground: stop completion of
innermost continue-scope-block and that's it. Works like charm for the
lambda-blocks (and, just by the side-effect, it does the local return).
You can use this continue in scenarios like:
a = {|v,k,c| ...};
...
label: collection.forEach(a);
But all the rest (labeled continue, labeled break, and unlabeled break which
is in fact implicitly-labeled-break (think about it; it not local and
relative, it break out of the respective control structure, so it is
lexically bound; thus implicitly labeled)) is lexically bound. These need
their scope-block be lexically enclosed inside the (explicit or implicit)
label.
So, in above example, you cannot use "break;". Because {|v,k,c| ...} is
present freely, not as part of its respective control structure, that is,
the function call. Of course the is true for labeled ones with label, since
{|v,k,c| ...} is not lexically present inside label: control structure.
Herby
P.S.: I really mean it. No joke. I just seem to write densely. Ask if there
is misunderstanding. Thanks.
-----Pôvodná správa-----
From: Brendan Eich
Sent: Saturday, January 14, 2012 11:05 PM
To: Herby Vojčík
Cc: François REMY ; es-discuss at mozilla.org
Subject: Re: Block Lambdas: break and continue
Herby Vojčík
January 14, 2012 1:46 PM
=== Brendan Eich wrote ===
This doesn't address Herby's TCP-violating wish for a non-return that
completes the block-lambda's control flow normally with a value (the message
to which I was replying). But you're right that it wouldn't violate TCP
either if we support it the same in a block statement as in a block-lambda
downward funarg.
===
No. it wasn't my primary wish to have a local return.
I understand, but (as you seem to concede below) if the TCP violation this
introduces is enough to kill it, I thought I'd argue against it on that
basis.
Sorry for seeming to miss your larger point -- I am following it closely too
;-).
I wanted to make break and/or continue _semantics_ for
lambda-block-loop-like-constructs.
Understood.
So since local return is already used for 'continue' in forEach, I just
generalized the syntax continue |expr|; (note the | bars, they are part of
the syntax)
Oh! I didn't know that. Often we (certainly I do this, others too) use bars
in such sketches as meta not concrete syntax. Thanks for clarifying.
Anyway, as others have written, this seems an abuse of 'continue'.
Also, you cannot avoid the exception-like effect if this is to propagate to
the forEach's internal loop. There's no loop visible to the spec or an
implementation's compiler. So this really is a throwing form. Again I do not
see how it can be "exception-less".
to do the local return, thereby functioning as a continue statement. (It
would be convenient to have local return, but not the local return itself
was the goal).
Local return violates TCP, so we should debate that vs. convenience if you
like. Sorry if that is not something you want to debate, but I think you
raised the issue directly and should respond.
You are true it breaks TCP. (It could be done so that it does not by
generalizing the syntax so it works in syntax-loop construct as well with
"end loop body with expr as the completion value" semantics; it's only btw,
it's too crazy to be considered, probably) So it cannot be used. :-/
Agreed. But let's debate the exception-less claim anyway, to each mutual
understanding.
By "this is de-facto continue" I was thinking more in higher semantic
level - continue as in "continue past this block", which in loops means "to
the next iteration" but beyond loops it may mean anything that is going to
happen after block completes.
The problem is the loop in Array forEach, or any such dynamically dispatched
caller of a block-lambda that is acting as a mapfun or iterator, is hidden
from static analysis.
So such a de-facto continue (I see what you mean now; I mentioned early
return from forEach callback as continue myself) must throw. It cannot be
exception-free.
Sorry to harp on this, I wonder if one of us is still misunderstanding
something.
Also, break is hard to do similar way, because I count out (automatically
set up) exceptions (I still live in the impression they are,
performance-wise, expensive).
Yes.
It seems to be possible to have "break |expr| label;" syntax working: if the
function calling the lambda-block is labeled, it should be possible to just
make it return the expr, but it is clumsy (and there is no label-less
version).
This reminds me of dherman's escape continuation proposal:
http://wiki.ecmascript.org/doku.php?id=strawman:return_to_label
We did not promote it from Strawman to Harmony status.
Overall, I am a bit optimistic, since (if I am not mistaken) lambda-blocks
only work inside its scope (as Self blocks, not as Smalltalk blocks), which
saves a lot of complications.
Block-lambdas can escape via the heap and be called later (so-called async.
callbacks). That is not a problem if they do not use return, break, or
continue. The TCP conformance for |this| is still a win. The completion
implicit return can be a win too.
/be
Herby
[Finally trimming overcites!]
/be
More information about the es-discuss
mailing list