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