Block Lambdas: break and continue
Grant Husbands <mailto:esdiscuss at grant.x43.net> January 13, 2012 7:29 PM Block lambdas have been a hot topic, recently, but there's a point of significant divergence between Ruby (which appears to be the inspiration)
Not Ruby alone, and not in any chauvinist my-language-is-better sense. Smalltalk is the original inspiration for Ruby blocks, and the correspondence principle has deep roots.
and the proposed solution, in the handling of continue (called 'next', in Ruby) and 'break'.
To whit: In Ruby, 'next' will end the current run (iteration) of the block, and 'break' will (somehow) terminate the method lexically connected with the block. It can be claimed that this is more intuitive than the current proposal, which aims to make 'break' and 'continue' propagate through block lambdas in the same way 'return' would.
"Intuitive" depends on intuition, which is not well-defined. Do you mean a Rubyist might expect different behavior for break? That is possible but JS ain't Ruby and break should not change to do something like what it does in Ruby (and we aren't defining a next equivalent for JS).
Ruby does also support syntactic loops and the same keywords therein and so directly violates Tennent's Correspondence Principle, even though such has been touted as a core reason for the construct. Instead, I believe it reasonable to invoke intuition in this matter. It is intuitive for 'return' to return a value from the lexically enclosing method and it is intuitive for 'continue' to commence the next iteration of the current loop,
Wait, why do you think break and continue without label operands do anything other than break from the nearest enclosing loop (or switch or labeled statement if break), or continue the nearest enclosing loop? The proposal specifies this.
function find_odds_in_arrays(list, // array of arrays skip) // if found, skip rest { let a = []; for (let i = 0; i < list.length; i++) { list[i].forEach { |e| if (e === skip) { continue; // continue the for loop } if (e & 1) { a.push(e); } } } return a; }
function find_more_odds(list, stop) { let a = []; for (let i = 0; i < list.length; i++) { list[i].forEach { |e| if (e === stop) { break; // break from the for loop } if (e & 1) { a.push(e); } } } return a; }
however that loop is constructed.
What do you mean by this? The spec talks about nearest enclosing loop or relevant control structure in the source code. Are you talking about internal loops in implementations (dynamically dispatched at that) of methods that take block-lambdas as arguments? I.e.
function find_first_odd(a) { a.forEach { |e, i| if (e & 1) return i; } // returns from function return -1; }
The Array.prototype.forEach method's internal implementation is its business, and a break instead of the return would be a static error in this example. It would not be a dynamic throw-like construct that is caught by forEach's implementation.
I think it’s a valid concern. The idea is: If I can implement my own loops (the nice-looking paren-free syntax feeds that illusion!) then I also want those loops to have break and continue. You could statically determine what construct, say, a break applies to and either throw a BreakException (if it applies to a lambda) or TCP-break (if it applies to an enclosing non-lambda loop). In the examples below, when I see a continue, I look for the innermost enclosing loop braces and the ones belong to list[i].forEach are definitely candidates.
Hello,
I think it is a concern, too, but from other point of view. By lambdaing the block, we got an added value of TCP (that is, "return" return from outer function) but we lost the ability of the lambda block to early local return the value itself.
Take example of forEach: it has no possibility of break; but it has possibility of continue - early local return from the block Take example of some: it has possibility of break - early local return of falsy value; it has possibility of continue - early local return of truy value
One answer to this is "multiline lambdas are not Pythonic", but I think this is not the right answer, lambda blocks are inspired by Smalltalk, in which multiline blocks are normal. Correct me if I am wrong and this is in fact the answer.
The second answer should be to somehow allow to early local return a value from the lambda block itself. Since early local return are often used in loops to perform "break" or "continue" functionality, I'd suggest to allow break (Expression) and continue (Expression) and have them doing the same effect, that is, to return the value of expression from the lambda block itself. It is up to user which one to use based on which is more intent revealing per each use.
Herby
-----Pôvodná správa---
I should have been clearer in my original message; I'll avoid mentioning Ruby directly, again, as it misdirects conversation.
Though I have no strong opinion on block-lambdas in javascript, I will try to restate my original position, for clarity: Block lambdas, as currently described in the proposal and in the community, have 'forEach' and similar as driving examples; I believe that a typical programmer would expect break or continue to directly affect that loop, and that that behaviour may be desirable.
Brendan Eich wrote:
"Intuitive" depends on intuition, which is not well-defined.
True, but I do believe in intuition without the bias of a source language. I understand that intuition can vary, so I might be aiming for 'average practitioner's intuition' or such.
Do you mean a Rubyist might expect different behavior for break?
I believe that the typical programmer, given the briefest exposure to block lambdas, would believe that 'continue' would behave the same way for a block-lambda-based loop as it does for any other loop.
Wait, why do you think break and continue without label operands do anything other than break from the nearest enclosing loop
What I meant was that, in the typical examples, 'forEach' is the nearest enclosing loop, and continue and break apparently bypass it.
The Array.prototype.forEach method's internal implementation is its business, and a break instead of the return would be a static error in this example. It would not be a dynamic throw-like construct that is caught by forEach's implementation.
Indeed, the way break would need to work could be a show-stopper for my proposed alteration, but I was trying to avoid implementation detail. My intent was to raise the divergence from (in my opinion) intuitive behaviour in the case of block-lambda-based loops.
, Grant Husbands.
I realized 'break' semantics is in no way enforceable in lambda-block control structure, and each early return is in fact 'continue' (which may stop the loop or whatever depending on the value). Also, since in lambda-block, | character could be used to distinguish it from keyword-loop-continue. So, what about allowing
continue |expression|;
syntax in addition to continue [label]; (with possibility to simple 'continue ||') and use it to denote return-from-lambda-block-only?
On Jan 13, 2012, at 9:04 PM, Axel Rauschmayer wrote:
I think it’s a valid concern. The idea is: If I can implement my own loops (the nice-looking paren-free syntax feeds that illusion!) then I also want those loops to have break and continue. You could statically determine what construct, say, a break applies to and either throw a BreakException (if it applies to a lambda) or TCP-break (if it applies to an enclosing non-lambda loop). In the examples below, when I see a continue, I look for the innermost enclosing loop braces and the ones belong to list[i].forEach are definitely candidates.
If I understand your suggestion, you're proposing that non-local break and continue should be exposed as standard exceptions, and then implementors of loop-like abstractions could choose to catch them. E.g. you could implement forEach as:
Array.prototype.forEach = function(f) {
for (let i = 0, n = this.length; i < n; i++) {
try {
f.call(this, this[i], i);
} catch (e) {
if (e instanceof BreakException)
break;
else if (e instanceof ContinueException)
continue;
else
throw e;
}
}
};
Whereas a function that does not want to expose whether it's using loops would simply do nothing with BreakException and ContinueException, and they would propagate out and you'd get the lexical scoping semantics. Meanwhile, break/continue with an explicit target would never be catch-able.
Did I understand your suggestion correctly?
This may not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what break
and continue
will do.
=== David Herman wrote ===
This may not violate TCP (I'm not quite sure), but I'm not enthusiastic
about the idea. The semantics is significantly more complicated, and it
requires you to understand whether a higher-order function like forEach is
catching these exceptions or not. So it becomes an additional part of the
API of a function. If someone doesn't document what they do with
BreakException and ContinueException, then writing callbacks you won't
actually be able to predict what break
and continue
will do.
What about the exception-less suggestion I put in? It should work in any loop construct with lambda-block, even if you must know a little about the loop implementation itself. That is, to be able to put:
continue |expression|;
as a statement in lambda block which instructs the lambda-block itself (not the outer function) to return the expression? This is the de-facto continue semantics (lambda-block, do return a value and the enclosing loop will continue to the next iteration (possibly stopping the loop if it chooses not to have more iterations)). It is not possible to enforce break in the same manner, but for continue, it is possible.
Herby
-----Pôvodná správa---
On Jan 14, 2012, at 9:12 AM, David Herman wrote:
On Jan 13, 2012, at 9:04 PM, Axel Rauschmayer wrote:
I think it’s a valid concern. The idea is: If I can implement my own loops (the nice-looking paren-free syntax feeds that illusion!) then I also want those loops to have break and continue. You could statically determine what construct, say, a break applies to and either throw a BreakException (if it applies to a lambda) or TCP-break (if it applies to an enclosing non-lambda loop). In the examples below, when I see a continue, I look for the innermost enclosing loop braces and the ones belong to list[i].forEach are definitely candidates.
I also think there is a valid concern here. The issue isn't whether the current break/continue statement semantics can be preserved when encapsulated by a block lambda. That is clearly possible because the semantics are tied to specific syntactically identifiable statements. The real issue is whether the same syntax can support break/continue-like semantics for user defined controls abstractions built using Block Lambdas. Put another way, can Block Lambdas based abstractions completely replace (at least conceptually) the built-in loop statements. This would seem desirable, but the answer isn't obviously yes -- hence the concern.
Consider the following function:
function forNum(start, end,increment=1,body={||}) { if (start > end) return; body(start); return forNum(start+increment,end,increment,body); }
User of the looping abstraction might reasonably expect to say something like:
forNum(first,last,1) {|n| if (n&1) continue; if (n==special) { doSpecialStuff(n); break; } doEven(n); };
with the meaning being essentially the same as they would get if they had used a for statement instead of the forNum function. Similarly for:
forNum(first,last,1) {|x| forNum(firstY(x),lastY(x),1) {|n| if (n&1) continue; if (n==special) { doSpecialStuff(n); break; } doEven(n); } };
where the continue and break apply to the inner loop rather than the outer loop. Before trying to figure out how to we could make these work (something like exceptions seems like a reasonable path) we should for look at some other possible use cases. Consider:
function logBlk(label, block) { logStream.put("starting "+label +Date()); block(); logStream.put("completed "+label +Date()); }
used in:
forNum(first,last,1) {|n| logBlk("process even values") {|| if (n&1) continue; if (n==special) { doSpecialStuff(n); break; } doEven(n); } };
This later use of forNum has essentially the same shape (think about them with forNum and logBlk renamed to func1 and func2) as the doubly nested example, yet the "binding" of the continue and break affects need to be different. How can this be achieved? Apparently there needs to be something additional in the implementation of forNum that won't occur in logBlk. Also, from a usage perspective, how does the user of these functions know that logBlk is transparent to break/continue while forNum is not? Presumably, this needs to be part of the usage "contract" of those functions. How can that be expressed? Is it implicit or explicit?
I'm not aware of very many languages that have addressed these issues, particularly in combination with syntactic looping constructs. The problem doesn't exist in Smalltalk because it doesn't have either break/continue or syntactic loops. It sounds like Ruby's solution may not be quite right. The most serious attempt that I know of to address this issue is in the BGGA closure proposal for Java: www.javac.info/closures-v05.html (see Loop Abstractions section), gafter.blogspot.com/2006/10/iterative-control-abstraction-user.html, tronicek.blogspot.com/2008/08/nonlocal-transfer.html, tronicek.blogspot.com/2008/08/modifier-for.html
If I understand your suggestion, you're proposing that non-local break and continue should be exposed as standard exceptions, and then implementors of loop-like abstractions could choose to catch them. ... Did I understand your suggestion correctly?
Yes. I was thinking about the BGGA closure proposal that Allen also linked to: www.javac.info/closures-v05.html
This may not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what
break
andcontinue
will do.
I don’t think it’s a must-have, but whenever you catch exceptions, you have similar issues.
Does it really make sense? If your goal is to return a value, using 'continue' is a lie. You may for exemple write "continue false" to break a loop or return from an event. It has nothing to do with a continue statement. A continue statement should be the complementary part of a break statement. If what you want is a way to return a value, you need a 'return' statment. Since 'return' returns from callee function, you need a modifier.
// exemple of bad code using continue 'value'
function someFunc(iterator) {
document.addEventListener({|e|
if (e.blablablabla) {
continue false;
}
});
}
My random thoughts would be:
return with false;
let return = false;
...
Another option is a specific way to break/continue from a code pattern. What about 'throw break' / 'throw continue'?
François
-----Message d'origine---
Herby Vojčík <mailto:herby at mailbox.sk> January 14, 2012 10:42 AM === David Herman wrote === This may not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what
break
andcontinue
will do.What about the exception-less suggestion I put in? It should work in any loop construct with lambda-block, even if you must know a little about the loop implementation itself. That is, to be able to put:
continue |expression|;
Who says the block-lambda is being called from a loop at all? Why should use-cases that want an early result and completion have to use continue, which is for loops?
Worse, this violates TCP. Now you copy and paste this block-lambda code back into a block statement to refactor the other direction, and no such "here is the completion value, do not flow past this point in the block" semantics obtain.
as a statement in lambda block which instructs the lambda-block itself (not the outer function) to return the expression? This is the de-facto continue semantics (lambda-block, do return a value and the enclosing loop will continue to the next iteration (possibly stopping the loop if it chooses not to have more iterations)).
No it's not. There is no de-facto continue semantics for block-lambdas because they haven't been prototyped. For block statements, no such continue semantics exists.
It is not possible to enforce break in the same manner, but for continue, it is possible.
It's possible to abuse any existing keyword, but first: why must there be a new TCP violation? Block-lambda bodies are often expressions, or if statements, then short/functional-style statements, not large bodies demonstrating early-normal-completion opportunities.
We should not eliminate TCP violations only to add new ones, especially without any evidence they're needed and pay their way. Otherwise we'll get an infinite regress of TCP-pure-then-add-new-exceptions-and-repeat additions.
Axel Rauschmayer <mailto:axel at rauschma.de> January 14, 2012 12:35 PM
This may not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what
break
andcontinue
will do.
See Allen's post for how this does not compose well.
I don’t think it’s a must-have, but whenever you catch exceptions, you have similar issues.
Sorry, termination style exception handling is pretty much a failure outside of local protocols with automated catching (for/of in Harmony loops, comprehensions, and generator expressions).
People build local ad-hoc try/catch machines, e.g. for generator schedulers, and those can be managed even without syntax, but these "kernels" require expertise and careful API design.
But in general when you see a try/catch, the catch is empty and the try is because of a call out of module or ownable unit of code, into some hostile subsystem or "other" that has in the past thrown a random exception.
IOW, termination-style exception handling does not scale. It's also a dynamic thing, like magic return codes. Intervening functions on the call stack with no necessary static relation to one another may have to try and catch (or at least try and finally) to unwind-protect. Not for memory management, of course, but for other mandatory protocols such as RAII patterns.
So I'm against adding BreakException and ContinueException and defining block-lambda break and continue in terms of throw. As Dave and Allen argued, this enlarges the contract of every possible intervening function to include exceptions. Exceptions are usually ignored at high level, used only locally (across shallow continuation calls).
We should avoid adding dependencies on exceptions that must be managed by arbitrarily large codebases and arbitrarily deep callstacks.
If we want to avoid to break TCP, we can go with “throw break;” and “throw continue;”. It would throw a new BreakException or a new ContinueException, from the place where they are executed. If it’s outside a block lambda, it’s outside a block lambda. It doesn’t matter.
But it would set a “standard” for breaking throug ‘function loops’. François
From: Brendan Eich Sent: Saturday, January 14, 2012 9:51 PM To: Herby Vojčík Cc: es-discuss at mozilla.org Subject: Re: Block Lambdas: break and continue
Herby Vojčík
January 14, 2012 10:42 AM
=== David Herman wrote ===
This may not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what break
and continue
will do.
What about the exception-less suggestion I put in? It should work in any loop construct with lambda-block, even if you must know a little about the loop implementation itself. That is, to be able to put:
continue |expression|;
Who says the block-lambda is being called from a loop at all? Why should use-cases that want an early result and completion have to use continue, which is for loops?
Worse, this violates TCP. Now you copy and paste this block-lambda code back into a block statement to refactor the other direction, and no such "here is the completion value, do not flow past this point in the block" semantics obtain.
as a statement in lambda block which instructs the lambda-block itself (not the outer function) to return the expression? This is the de-facto continue semantics (lambda-block, do return a value and the enclosing loop will continue to the next iteration (possibly stopping the loop if it chooses not to have more iterations)).
No it's not. There is no de-facto continue semantics for block-lambdas because they haven't been prototyped. For block statements, no such continue semantics exists.
It is not possible to enforce break in the same manner, but for continue, it is possible.
It's possible to abuse any existing keyword, but first: why must there be a new TCP violation? Block-lambda bodies are often expressions, or if statements, then short/functional-style statements, not large bodies demonstrating early-normal-completion opportunities.
We should not eliminate TCP violations only to add new ones, especially without any evidence they're needed and pay their way. Otherwise we'll get an infinite regress of TCP-pure-then-add-new-exceptions-and-repeat additions.
/be
Herby
-----Pôvodná správa----- From: David Herman Sent: Saturday, January 14, 2012 6:12 PM To: Axel Rauschmayer Cc: Brendan Eich ; es-discuss at mozilla.org Subject: Re: Block Lambdas: break and continue
On Jan 13, 2012, at 9:04 PM, Axel Rauschmayer wrote:
If I understand your suggestion, you're proposing that non-local break and continue should be exposed as standard exceptions, and then implementors of loop-like abstractions could choose to catch them. E.g. you could implement forEach as:
Array.prototype.forEach = function(f) {
for (let i = 0, n = this.length; i < n; i++) {
try {
f.call(this, this[i], i);
} catch (e) {
if (e instanceof BreakException)
break;
else if (e instanceof ContinueException)
continue;
else
throw e;
}
}
};
Whereas a function that does not want to expose whether it's using loops would simply do nothing with BreakException and ContinueException, and they would propagate out and you'd get the lexical scoping semantics. Meanwhile, break/continue with an explicit target would never be catch-able.
Did I understand your suggestion correctly?
This may not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what break
and continue
will do.
Dave
es-discuss mailing list es-discuss at mozilla.org, mail.mozilla.org/listinfo/es-discuss
es-discuss mailing list es-discuss at mozilla.org, mail.mozilla.org/listinfo/es-discuss
David Herman January 14, 2012 9:12 AM
If I understand your suggestion, you're proposing that non-local break and continue should be exposed as standard exceptions, and then implementors of loop-like abstractions could choose to catch them. E.g. you could implement forEach as:
Array.prototype.forEach = function(f) { for (let i = 0, n = this.length; i < n; i++) { try { f.call(this, this[i], i); } catch (e) { if (e instanceof BreakException) break; else if (e instanceof ContinueException) continue; else throw e; } } };
Whereas a function that does not want to expose whether it's using loops would simply do nothing with BreakException and ContinueException, and they would propagate out and you'd get the lexical scoping semantics. Meanwhile, break/continue with an explicit target would never be catch-able.
Did I understand your suggestion correctly?
This may not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what break
and continue
will do.
Dave
Axel Rauschmayer January 13, 2012 9:04 PM I think it’s a valid concern. The idea is: If I can implement my own loops (the nice-looking paren-free syntax feeds that illusion!) then I also want those loops to have break and continue. You could statically determine what construct, say, a break applies to and either throw a BreakException (if it applies to a lambda) or TCP-break (if it applies to an enclosing non-lambda loop). In the examples below, when I see a continue, I look for the innermost enclosing loop braces and the ones belong to list[i].forEach are definitely candidates.
What you said about ‘large-scale’ try/catch seems irrevelant to me. If you make a function that takes a callback as an argument, you already HAVE to use a try-catch, since you have to prevent any faillure in the code of the callback since you can’t trust it, even if he’s empty (as you noted). So, the BreakException has no chance to go outside the function who called the block lambda.
If you want to implement a function that loops, you just need to add some code to handle BreakException and ContinueException. If you don’t want, you just don’t use it. It’s up to you. From: Brendan Eich Sent: Saturday, January 14, 2012 10:00 PM To: Axel Rauschmayer Cc: es-discuss at mozilla.org Subject: Re: Block Lambdas: break and continue
Axel Rauschmayer January 14, 2012 12:35 PM
This may not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what break
and continue
will do.
See Allen's post for how this does not compose well.
I don’t think it’s a must-have, but whenever you catch exceptions, you have similar issues.
Sorry, termination style exception handling is pretty much a failure outside of local protocols with automated catching (for/of in Harmony loops, comprehensions, and generator expressions).
People build local ad-hoc try/catch machines, e.g. for generator schedulers, and those can be managed even without syntax, but these "kernels" require expertise and careful API design.
But in general when you see a try/catch, the catch is empty and the try is because of a call out of module or ownable unit of code, into some hostile subsystem or "other" that has in the past thrown a random exception.
IOW, termination-style exception handling does not scale. It's also a dynamic thing, like magic return codes. Intervening functions on the call stack with no necessary static relation to one another may have to try and catch (or at least try and finally) to unwind-protect. Not for memory management, of course, but for other mandatory protocols such as RAII patterns.
So I'm against adding BreakException and ContinueException and defining block-lambda break and continue in terms of throw. As Dave and Allen argued, this enlarges the contract of every possible intervening function to include exceptions. Exceptions are usually ignored at high level, used only locally (across shallow continuation calls).
We should avoid adding dependencies on exceptions that must be managed by arbitrarily large codebases and arbitrarily deep callstacks.
François REMY <mailto:fremycompany_pub at yahoo.fr> January 14, 2012 1:01 PM If we want to avoid to break TCP, we can go with “throw break;” and “throw continue;”.
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.
It would throw a new BreakException or a new ContinueException, from the place where they are executed. If it’s outside a block lambda, it’s outside a block lambda. It doesn’t matter.
Yes, this would avoid TCP violations but not carry a return value
Two possibilities (but I’m not entirely sure how much sense they make):
-
Use a keyword that enables custom break and continue (problem: "for" clashes with iterators): for mycollection.each({ | x | if (x === 0) break })
-
Standardize a BreakException. Then breaking isn’t the loop’s responsibility, any more.
continue
as an early return seems less important, because you can use break as follows: mycoll.forEach { | x | block: { if (x < 0) { break block; // "continue" } // do more with x here } }
François REMY <mailto:fremycompany_pub at yahoo.fr> January 14, 2012 1:14 PM What you said about ‘large-scale’ try/catch seems irrevelant to me.
Not at all, since you among others are proposing break and continue exceptions (whether thrown explicitly via new syntax, or implicitly by break and continue statements). This as Allen's examples showed requires scaling across more than one activation, and across dynamic dispatch (no static-only break or continue as in the strawman).
If you make a function that takes a callback as an argument, you already HAVE to use a try-catch,
Not so. First, real code does not use try/catch in such callback calling functions at all. The Array extras do not try/catch, whether they are implemented in JS or some host VM implementation language (usually C++). Exceptions propagate and unwind without any try-guarding or catch/finally handling.
since you have to prevent any faillure in the code of the callback since you can’t trust it, even if he’s empty (as you noted).
I noted no such thing.
So, the BreakException has no chance to go outside the function who called the block lambda.
The problem is that all callback-calling functions that "loop" (however defined) must now try/catch, where before they did not. Yes, there is a benefit: in your proposal one can "throw break" or "throw continue" from a block-lambda provided as the callback, if one knows the API well and is sure of which calling function was dispatched dynamically.
For other functions, and when composing deeper callstacks (as in Allen's logging example), then one has to think about whether to try or not. It may be that the thrown break or continue can propagate without handling, but if the intervening function is in a loop, then not so: that function must try and rethrow in its catch clause, in order to get the "throw break" to reach the grand-caller of the block-lambda. This is an onerous requirement in the limit!
If you want to implement a function that loops, you just need to add some code to handle BreakException and ContinueException.
Yes, but handle how? This is a new and harsh requirement. Callback-calling (downward-funarg calling, let's say) functions written yesterday and today simply do not try/catch because a callback might throw. Exception handling is seldom used in the large except to void the exception.
Thanks for your reply. I think you misunderstood the whole concept. The idea would not to implement that for Arrays. Arrays already have their own semantics.
I think the idea about Block Lamdba is to introduce functionnal programming concepts to ECMAScript. It’s it’s the case, developers will want to implement their own kind of Head|Queue stream. For exemple, a webworker computing values and calling the callback with the computed value each time he output a new one (using a postMessage channel). The problem is that you may need to have a way to say to the WebWorker “OK, I want to break the loop, I’ve what I need, please stop computing and free memory.” You could use your own “throw” pattern but using a standard one could be useful for code reuse.
I hope I was clear, François
From: Brendan Eich Sent: Saturday, January 14, 2012 10:16 PM To: François REMY Cc: Herby Vojčík ; es-discuss at mozilla.org Subject: Re: Block Lambdas: break and continue
François REMY January 14, 2012 1:01 PM If we want to avoid to break TCP, we can go with “throw break;” and “throw continue;”.
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.
It would throw a new BreakException or a new ContinueException, from the place where they are executed. If it’s outside a block lambda, it’s outside a block lambda. It doesn’t matter.
Yes, this would avoid TCP violations but not carry a return value -- Herby's wish.
But it would set a “standard” for breaking throug ‘function loops’.
I considered this in drafting the block-lambda revival strawman. Other languages have gone here. Nevertheless, I would like to leave it out (remember N. Wirth on language design). It adds more complexity for a use-case that I bet is rare (in any case it needs credible demonstration of being quite common).
The complexity in the semantics is one issue Dave raised. This corresponds to complexity for optimizing engines, compared to the purely static break/continue semantics in the strawman.
Finally, the Array extras ship sailed. People already have to use some or every in lieu of a break-from-forEach. Using a function callback with forEach, one needs only to return to simulate continue. Now if we do standardize block-lambdas and throw break or throw continue, we certainly can elaborate the extras to catch these exceptions.
Such a more complex design seems workable with the costs noted above. But will the benefits really outweigh those costs? I doubt it. First, Array forEach and other uses will continue to use functions for quite a while, or else a compiler from new standard JS to old. In the compiler case, throw and try/catch will be required, and the compiler will have to monkey-patch the extras to deal with the new exceptions. This will be a performance killer, and no fun to debug.
So my thinking remains that we are better off, when in doubt, leaving reified break and continue exceptions "out".
/be
François
From: Brendan Eich Sent: Saturday, January 14, 2012 9:51 PM To: Herby Vojčík Cc: es-discuss at mozilla.org Subject: Re: Block Lambdas: break and continue
Herby Vojčík
January 14, 2012 10:42 AM
=== David Herman wrote ===
This *may* not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what `break` and `continue` will do.
===
What about the exception-less suggestion I put in? It should work in any loop construct with lambda-block, even if you must know a little about the loop implementation itself. That is, to be able to put:
continue |expression|;
Who says the block-lambda is being called from a loop at all? Why should use-cases that want an early result and completion have to use continue, which is for loops?
Worse, this violates TCP. Now you copy and paste this block-lambda code back into a block statement to refactor the other direction, and no such "here is the completion value, do not flow past this point in the block" semantics obtain.
as a statement in lambda block which instructs the lambda-block itself (not the outer function) to return the expression? This is the de-facto continue semantics (lambda-block, do return a value and the enclosing loop will continue to the next iteration (possibly stopping the loop if it chooses not to have more iterations)).
No it's not. There is no de-facto continue semantics for block-lambdas because they haven't been prototyped. For block statements, no such continue semantics exists.
It is not possible to enforce break in the same manner, but for continue, it is possible.
It's possible to abuse any existing keyword, but first: why must there be a new TCP violation? Block-lambda bodies are often expressions, or if statements, then short/functional-style statements, not large bodies demonstrating early-normal-completion opportunities.
We should not eliminate TCP violations only to add new ones, especially without any evidence they're needed and pay their way. Otherwise we'll get an infinite regress of TCP-pure-then-add-new-exceptions-and-repeat additions.
/be
Herby
-----Pôvodná správa----- From: David Herman
Sent: Saturday, January 14, 2012 6:12 PM
To: Axel Rauschmayer
Cc: Brendan Eich ; es-discuss at mozilla.org
Subject: Re: Block Lambdas: break and continue
On Jan 13, 2012, at 9:04 PM, Axel Rauschmayer wrote:
If I understand your suggestion, you're proposing that non-local break and continue should be exposed as standard exceptions, and then implementors of loop-like abstractions could choose to catch them. E.g. you could implement forEach as:
Array.prototype.forEach = function(f) {
for (let i = 0, n = this.length; i < n; i++) {
try {
f.call(this, this[i], i);
} catch (e) {
if (e instanceof BreakException)
break;
else if (e instanceof ContinueException)
continue;
else
throw e;
}
}
};
Whereas a function that does *not* want to expose whether it's using loops would simply do nothing with BreakException and ContinueException, and they would propagate out and you'd get the lexical scoping semantics. Meanwhile, break/continue with an explicit target would never be catch-able.
Did I understand your suggestion correctly?
This *may* not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what `break` and `continue` will do.
Dave
_______________________________________________
es-discuss mailing list
es-discuss at mozilla.org
https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
es-discuss at mozilla.org
https://mail.mozilla.org/listinfo/es-discuss
David Herman
January 14, 2012 9:12 AM
If I understand your suggestion, you're proposing that non-local break and continue should be exposed as standard exceptions, and then implementors of loop-like abstractions could choose to catch them. E.g. you could implement forEach as:
Array.prototype.forEach = function(f) {
for (let i = 0, n = this.length; i < n; i++) {
try {
f.call(this, this[i], i);
} catch (e) {
if (e instanceof BreakException)
break;
else if (e instanceof ContinueException)
continue;
else
throw e;
}
}
};
Whereas a function that does *not* want to expose whether it's using loops would simply do nothing with BreakException and ContinueException, and they would propagate out and you'd get the lexical scoping semantics. Meanwhile, break/continue with an explicit target would never be catch-able.
Did I understand your suggestion correctly?
This *may* not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what `break` and `continue` will do.
Dave
Axel Rauschmayer
January 13, 2012 9:04 PM
I think it’s a valid concern. The idea is: If I can implement my own loops (the nice-looking paren-free syntax feeds that illusion!) then I also want those loops to have break and continue. You could statically determine what construct, say, a break applies to and either throw a BreakException (if it applies to a lambda) or TCP-break (if it applies to an enclosing non-lambda loop). In the examples below, when I see a continue, I look for the innermost enclosing loop braces and the ones belong to list[i].forEach are definitely candidates.
Brendan Eich <mailto:brendan at mozilla.org> January 14, 2012 1:24 PM Exception handling is seldom used in the large except to void the exception.
And then only near or at a top-level event loop.
Exception handling is the issue here, because of the proposal to add reifed break and continue as thrown exceptions. How exception handling is practiced (usually not) is highly relevant.
Wishing for JS users to happily add more exception handling won't make that work happen, because it costs a lot. That it costs a lot (too much in my view) is enough to consider not adding new exceptions that must be considered when designing all "looping" and "wrapping" (AOP) APis.
François REMY <mailto:fremycompany_pub at yahoo.fr> January 14, 2012 1:31 PM Thanks for your reply. I think you misunderstood the whole concept.
Could be, or perhaps you misunderstood my reply to Herby's message, which had nothing to do with the ax you are grinding :-|.
The idea would not to implement that for Arrays. Arrays already have their own semantics.
Look back up the thread (or down the cited messages below -- but I think I'll trim the overciting) and see Axel bringing up Array forEach as the use-case for dynamic break and continue semantics in block-lambdas.
I appreciate your repair via "throw break" and "throw continue" to restore TCP. That's not at issue.
What is at issue is the cost/benefit analysis, which does depend on developer ergonomics of exceptions, including as block-lambdas that throw break or throw continue might work (or not) with Array extras.
You cannot a priori exclude complex scenarios by wishing for better programmers, or programmers who use new functional styles and only new APIs, and buy into the complexity of the new exceptions. At least, Ecma TC39 cannot.
We have to consider how poorly or well exceptions are used today, how developers might use them tomorrow, what reasonable (see Axel's post) expectations they might have for Array extras called with block-lambdas.
The throw break and throw continue ideas are coherent and we could design them into a future spec. I have no doubt about this. But I do not think the complexity, for developers first but also for implementors (and last for the spec editors) is worth it.
I think the idea about Block Lamdba is to introduce functionnal programming concepts to ECMAScript.
Not really. JS is used in function-programming styles (there's more than one) already.
What block-lambdas aim to do is provide much sweeter downward-funarg syntax and semantics, which aids refactoring and reasoning about such "synchronous callback" code.
Block-lambdas may be used with asynchronous callback code too, with the benefits of |this| invariance and completion-return.
This is not a big new functional-programming concept push. It's a usability and safety (wrong-this bugs are bad) win for many (not all) uses of function-expressions-as-callbacks we see today.
It’s it’s the case, developers will want to implement their own kind of Head|Queue stream. For exemple, a webworker computing values and calling the callback with the computed value each time he output a new one (using a postMessage channel). The problem is that you may need to have a way to say to the WebWorker “OK, I want to break the loop, I’ve what I need, please stop computing and free memory.” You could use your own “throw” pattern but using a standard one could be useful for code reuse.
I'd say that differently: for such hard cases, throw already exists. The burden is on the proposer to show this is common enough to justify making break and continue, or in your proposal, new "throw break" and "throw continue" forms, throw new exceptions.
The problem is not just the cost of adding new forms (let's say your proposal wins -- it is much better than redefining break and continue to throw).
The issue I'm raising is the consequence on "looping" and "wrapper" functions in common use (whether Array forEach or JQuery each or something new next year or bespoke in a private codebase) of introducing such new exceptions as standards.
Let the worker hard case use "throw" and its own sentinel object, and let's see how common and non-hard this case becomes. I doubt it will grow to become much seen.
(Indeed I hope it is supplanted by better concurrency structures than workers, and we're laboring to develop such things. But whatever happens there, this use-case is not obviously common enough to be worth a change with global effects.)
=== 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 wanted to make break and/or continue semantics for lambda-block-loop-like-constructs. 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) 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).
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. :-/
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.
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). 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 "break |expr| label" may, too, be generalized if TCP is of concern; again just BTW).
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.
Herby
-----Pôvodná správa---
On Jan 14, 2012, at 1:31 PM, François REMY wrote:
Thanks for your reply. I think you misunderstood the whole concept. The idea would not to implement that for Arrays. Arrays already have their own semantics.
I think the idea about Block Lamdba is to introduce functionnal programming concepts to ECMAScript. It’s it’s the case, developers will want to implement their own kind of Head|Queue stream. For exemple, a webworker computing values and calling the callback with the computed value each time he output a new one (using a postMessage channel). The problem is that you may need to have a way to say to the WebWorker “OK, I want to break the loop, I’ve what I need, please stop computing and free memory.” You could use your own “throw” pattern but using a standard one could be useful for code reuse.
Actually, I think having a standard patten is a hazard. The reason is that there may be multiple additional and dynamically determined control abstraction layers (and activation records) between the "WebWorker" iteration abstraction the actual evaluation of the Block Lambda that does the "throw". If you have a standard exception that is used for this purpose then you have to worry about the intermediate layer using that same standard.
As Dave and I both mentioned. This sort of escape mechanism really needs to be uniquely defined as part of the contract of each iterator abstraction that supports. Defining a specific (and unique) exception type is a fine way to do it. For example, my forNum function could define a privateName valued property that does that:
forNum.breaker = Name.create(); //sometimes hoisting is nice function forNum(start, end,increment=1,body={||}) { if (start > end) return; try { body(start); } catch (e if e is forNum.breaker) {return}; // probably better to factor this hander into a non-recursive wrapper return forNum(start+increment,end,increment,body); }
which could be used like:
forNum(first,last,1) {|n| if (n==0) throw forNum.breaker; ... }
The above style forces the iteration abstraction to explicitly define its "break" contract and forces the user of the iteration abstraction to use the contract specified mechanism. No tripping over undocumented or unexpected usage of defaults.
Herby Vojčík <mailto:herby at mailbox.sk> 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:
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!]
Yes, I did not say I was going to struggle about that functionnality. I just read some of the many threads of this mailing list and, when I encounter an idea that resonnate in my brain, I do my best to play the Devils’ advocate when it gets rejected, just to push the discussion forward.
I find that many ideas proposed on this mailing list are discarded too quickly and desserve some more ‘agnostic’ analysis. If someone reached this mailing list and posted something in it, it’s probably not by hazard, and many people probably had the same idea but didn’t bother to reach the comitee because of the inertial barrier. Pushing the idea further may or may not lead to something at the end, but it will drive new ideas. Maybe one of them will be worth a shot. Or maybe it’ll just change the mind of someone. It’s my philosophy, at least. I just hope you don’t consider me as someone who tries his best to argue with you, it’s clearly not my goal.
Best , François
From: Brendan Eich Sent: Saturday, January 14, 2012 10:44 PM To: François REMY Cc: es-discuss at mozilla.org Subject: Re: Block Lambdas: break and continue
François REMY January 14, 2012 1:31 PM Thanks for your reply. I think you misunderstood the whole concept.
Could be, or perhaps you misunderstood my reply to Herby's message, which had nothing to do with the ax you are grinding :-|.
The idea would not to implement that for Arrays. Arrays already have their own semantics.
Look back up the thread (or down the cited messages below -- but I think I'll trim the overciting) and see Axel bringing up Array forEach as the use-case for dynamic break and continue semantics in block-lambdas.
I appreciate your repair via "throw break" and "throw continue" to restore TCP. That's not at issue.
What is at issue is the cost/benefit analysis, which does depend on developer ergonomics of exceptions, including as block-lambdas that throw break or throw continue might work (or not) with Array extras.
You cannot a priori exclude complex scenarios by wishing for better programmers, or programmers who use new functional styles and only new APIs, and buy into the complexity of the new exceptions. At least, Ecma TC39 cannot.
We have to consider how poorly or well exceptions are used today, how developers might use them tomorrow, what reasonable (see Axel's post) expectations they might have for Array extras called with block-lambdas.
The throw break and throw continue ideas are coherent and we could design them into a future spec. I have no doubt about this. But I do not think the complexity, for developers first but also for implementors (and last for the spec editors) is worth it.
I think the idea about Block Lamdba is to introduce functionnal programming concepts to ECMAScript.
Not really. JS is used in function-programming styles (there's more than one) already.
What block-lambdas aim to do is provide much sweeter downward-funarg syntax and semantics, which aids refactoring and reasoning about such "synchronous callback" code.
Block-lambdas may be used with asynchronous callback code too, with the benefits of |this| invariance and completion-return.
This is not a big new functional-programming concept push. It's a usability and safety (wrong-this bugs are bad) win for many (not all) uses of function-expressions-as-callbacks we see today.
It’s it’s the case, developers will want to implement their own kind of Head|Queue stream. For exemple, a webworker computing values and calling the callback with the computed value each time he output a new one (using a postMessage channel). The problem is that you may need to have a way to say to the WebWorker “OK, I want to break the loop, I’ve what I need, please stop computing and free memory.” You could use your own “throw” pattern but using a standard one could be useful for code reuse.
I'd say that differently: for such hard cases, throw already exists. The burden is on the proposer to show this is common enough to justify making break and continue, or in your proposal, new "throw break" and "throw continue" forms, throw new exceptions.
The problem is not just the cost of adding new forms (let's say your proposal wins -- it is much better than redefining break and continue to throw).
The issue I'm raising is the consequence on "looping" and "wrapper" functions in common use (whether Array forEach or JQuery each or something new next year or bespoke in a private codebase) of introducing such new exceptions as standards.
Let the worker hard case use "throw" and its own sentinel object, and let's see how common and non-hard this case becomes. I doubt it will grow to become much seen.
(Indeed I hope it is supplanted by better concurrency structures than workers, and we're laboring to develop such things. But whatever happens there, this use-case is not obviously common enough to be worth a change with global effects.)
/be
I hope I was clear, François
From: Brendan Eich Sent: Saturday, January 14, 2012 10:16 PM To: François REMY Cc: Herby Vojčík ; es-discuss at mozilla.org Subject: Re: Block Lambdas: break and continue
François REMY
January 14, 2012 1:01 PM
If we want to avoid to break TCP, we can go with “throw break;” and “throw continue;”.
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.
It would throw a new BreakException or a new ContinueException, from the place where they are executed. If it’s outside a block lambda, it’s outside a block lambda. It doesn’t matter.
Yes, this would avoid TCP violations but not carry a return value -- Herby's wish.
But it would set a “standard” for breaking throug ‘function loops’.
I considered this in drafting the block-lambda revival strawman. Other languages have gone here. Nevertheless, I would like to leave it out (remember N. Wirth on language design). It adds more complexity for a use-case that I bet is rare (in any case it needs credible demonstration of being quite common).
The complexity in the semantics is one issue Dave raised. This corresponds to complexity for optimizing engines, compared to the purely static break/continue semantics in the strawman.
Finally, the Array extras ship sailed. People already have to use some or every in lieu of a break-from-forEach. Using a function callback with forEach, one needs only to return to simulate continue. Now if we do standardize block-lambdas and throw break or throw continue, we certainly can elaborate the extras to catch these exceptions.
Such a more complex design seems workable with the costs noted above. But will the benefits really outweigh those costs? I doubt it. First, Array forEach and other uses will continue to use functions for quite a while, or else a compiler from new standard JS to old. In the compiler case, throw and try/catch will be required, and the compiler will have to monkey-patch the extras to deal with the new exceptions. This will be a performance killer, and no fun to debug.
So my thinking remains that we are better off, when in doubt, leaving reified break and continue exceptions "out".
/be
François
From: Brendan Eich
Sent: Saturday, January 14, 2012 9:51 PM
To: Herby Vojčík
Cc: es-discuss at mozilla.org
Subject: Re: Block Lambdas: break and continue
Herby Vojčík
January 14, 2012 10:42 AM
=== David Herman wrote ===
This *may* not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what `break` and `continue` will do.
===
What about the exception-less suggestion I put in? It should work in any loop construct with lambda-block, even if you must know a little about the loop implementation itself. That is, to be able to put:
continue |expression|;
Who says the block-lambda is being called from a loop at all? Why should use-cases that want an early result and completion have to use continue, which is for loops?
Worse, this violates TCP. Now you copy and paste this block-lambda code back into a block statement to refactor the other direction, and no such "here is the completion value, do not flow past this point in the block" semantics obtain.
as a statement in lambda block which instructs the lambda-block itself (not the outer function) to return the expression? This is the de-facto continue semantics (lambda-block, do return a value and the enclosing loop will continue to the next iteration (possibly stopping the loop if it chooses not to have more iterations)).
No it's not. There is no de-facto continue semantics for block-lambdas because they haven't been prototyped. For block statements, no such continue semantics exists.
It is not possible to enforce break in the same manner, but for continue, it is possible.
It's possible to abuse any existing keyword, but first: why must there be a new TCP violation? Block-lambda bodies are often expressions, or if statements, then short/functional-style statements, not large bodies demonstrating early-normal-completion opportunities.
We should not eliminate TCP violations only to add new ones, especially without any evidence they're needed and pay their way. Otherwise we'll get an infinite regress of TCP-pure-then-add-new-exceptions-and-repeat additions.
/be
Herby
-----Pôvodná správa----- From: David Herman
Sent: Saturday, January 14, 2012 6:12 PM
To: Axel Rauschmayer
Cc: Brendan Eich ; es-discuss at mozilla.org
Subject: Re: Block Lambdas: break and continue
On Jan 13, 2012, at 9:04 PM, Axel Rauschmayer wrote:
If I understand your suggestion, you're proposing that non-local break and continue should be exposed as standard exceptions, and then implementors of loop-like abstractions could choose to catch them. E.g. you could implement forEach as:
Array.prototype.forEach = function(f) {
for (let i = 0, n = this.length; i < n; i++) {
try {
f.call(this, this[i], i);
} catch (e) {
if (e instanceof BreakException)
break;
else if (e instanceof ContinueException)
continue;
else
throw e;
}
}
};
Whereas a function that does *not* want to expose whether it's using loops would simply do nothing with BreakException and ContinueException, and they would propagate out and you'd get the lexical scoping semantics. Meanwhile, break/continue with an explicit target would never be catch-able.
Did I understand your suggestion correctly?
This *may* not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what `break` and `continue` will do.
Dave
_______________________________________________
es-discuss mailing list
es-discuss at mozilla.org
https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
es-discuss at mozilla.org
https://mail.mozilla.org/listinfo/es-discuss
David Herman
January 14, 2012 9:12 AM
If I understand your suggestion, you're proposing that non-local break and continue should be exposed as standard exceptions, and then implementors of loop-like abstractions could choose to catch them. E.g. you could implement forEach as:
Array.prototype.forEach = function(f) {
for (let i = 0, n = this.length; i < n; i++) {
try {
f.call(this, this[i], i);
} catch (e) {
if (e instanceof BreakException)
break;
else if (e instanceof ContinueException)
continue;
else
throw e;
}
}
};
Whereas a function that does *not* want to expose whether it's using loops would simply do nothing with BreakException and ContinueException, and they would propagate out and you'd get the lexical scoping semantics. Meanwhile, break/continue with an explicit target would never be catch-able.
Did I understand your suggestion correctly?
This *may* not violate TCP (I'm not quite sure), but I'm not enthusiastic about the idea. The semantics is significantly more complicated, and it requires you to understand whether a higher-order function like forEach is catching these exceptions or not. So it becomes an additional part of the API of a function. If someone doesn't document what they do with BreakException and ContinueException, then writing callbacks you won't actually be able to predict what `break` and `continue` will do.
Dave
Axel Rauschmayer
January 13, 2012 9:04 PM
I think it’s a valid concern. The idea is: If I can implement my own loops (the nice-looking paren-free syntax feeds that illusion!) then I also want those loops to have break and continue. You could statically determine what construct, say, a break applies to and either throw a BreakException (if it applies to a lambda) or TCP-break (if it applies to an enclosing non-lambda loop). In the examples below, when I see a continue, I look for the innermost enclosing loop braces and the ones belong to list[i].forEach are definitely candidates.
François REMY <mailto:fremycompany_pub at yahoo.fr> January 14, 2012 2:06 PM Yes, I did not say I was going to struggle about that functionnality. I just read some of the many threads of this mailing list and, when I encounter an idea that resonnate in my brain, I do my best to play the Devils’ advocate when it gets rejected, just to push the discussion forward.
Perfectly fine and what this list is for. Please accept my apology if I seemed to reject things too quickly. We need to give "throw break" and "throw continue" full consideration.
I find that many ideas proposed on this mailing list are discarded too quickly and desserve some more ‘agnostic’ analysis. If someone reached this mailing list and posted something in it, it’s probably not by hazard, and many people probably had the same idea but didn’t bother to reach the comitee because of the inertial barrier. Pushing the idea further may or may not lead to something at the end, but it will drive new ideas. Maybe one of them will be worth a shot. Or maybe it’ll just change the mind of someone. It’s my philosophy, at least. I just hope you don’t consider me as someone who tries his best to argue with you, it’s clearly not my goal.
It's funny you should write this (I agree completely) because I often hear people complain that es-discuss is too busy or noisy (it's not compared to many lists I follow).
Exploring larger design spaces, doing though experiments based on research and precedent from solid existing languages that are "close" to JS in semantics -- these are welcome here and I gladly put up with some noise to get them. And in the case of this thread, I see very low noise.
In any case I don't see how to avoid a bit of noise in general, long with perhaps many negative results (which are "good" in science, and which we should remember to avoid going in bad circles), to get the benefits of exploring the larger design space and considering strong precedent.
So, onward!
Brendan Eich wrote:
Exception handling is seldom used in the large except to void the exception.
And then only near or at a top-level event loop.
Exception handling is the issue here, because of the proposal to add reifed break and continue as thrown exceptions. How exception handling is practiced (usually not) is highly relevant.
I agree that exceptions might not be the way to handle it. I've seen it noted that something like these would work under the existing proposal:
forEach(arr) { |a| skip: { // ... if (...) break skip; // Equivalent to continue } };
end: forEach(arr) { |s| // ... if (...) break end; // Equivalent to break };
The mechanism by which they work may give a more agreeable path for implementation of the new kind of end/break, perhaps along with a keyword near the forEach to enable the behaviour. It would also mean that break/continue could still be matched with their jump-sites at compile time. There'd be the caveat that the iterating function would have no control over that flow, but that is already the case for continue/break/return under the existing proposal.
To try to be clear, I'm implying a syntax akin to this could give the desired break/continue functionality for lambda-block-based loops, and could essentially desugar to the above: for forEach(arr) { |a| // ... if (...) continue; if (...) break; };
It would probably need alteration to not conflict with other recent syntax.
, Grant Husbands.
Grant Husbands <mailto:esdiscuss at grant.x43.net> January 14, 2012 2:51 PM
I agree that exceptions might not be the way to handle it. I've seen it noted that something like these would work under the existing proposal:
forEach(arr) { |a| skip: { // ... if (...) break skip; // Equivalent to continue } };
end: forEach(arr) { |s| // ... if (...) break end; // Equivalent to break };
Yes (just to confirm), these work in the strawman today. But (in case there's confusion; signs of it on twitter and here) one doesn't need labels to break from a true loop/switch or continue a true loop. This is only the forEach (and similar) case.
The mechanism by which they work may give a more agreeable path for implementation of the new kind of end/break, perhaps along with a keyword near the forEach to enable the behaviour.
Right, Axel's "for " prefix from several messages back:
for mycollection.each({ | x | if (x === 0) break })
That is an interesting idea, I meant to reply to it.
Of course we want to uphold TCP, which constrains the design. What works here must work in a regular block statement as body of such a loopish structure.
It would also mean that break/continue could still be matched with their jump-sites at compile time. There'd be the caveat that the iterating function would have no control over that flow, but that is already the case for continue/break/return under the existing proposal.
Yes, and that is the flip side of the win of avoiding reified break and continue exceptions: you can't handle them, so you don't have to handle them (or worry about it).
To try to be clear, I'm implying a syntax akin to this could give the desired break/continue functionality for lambda-block-based loops, and could essentially desugar to the above: for forEach(arr) { |a| // ... if (...) continue; if (...) break; };
It would probably need alteration to not conflict with other recent syntax.
It's ok as a statement extension since for currently must be followed by ( in statement contex. The paren-free for/of comprehension and generator expression forms would be problematic if we wanted such an extension for them, but we do not.
However, paren-free as a relaxation of syntax, as I've proposed and am still working on in the background, would have a problem with this for helper {|x| ...} idea. More when I reply to Axel in a bit.
Axel Rauschmayer <mailto:axel at rauschma.de> January 14, 2012 1:19 PM Two possibilities (but I’m not entirely sure how much sense they make):
- Use a keyword that enables custom break and continue (problem: "for" clashes with iterators): for mycollection.each({ | x | if (x === 0) break })
Really clashes with paren-free, which is not ready for promotion from strawman. In a comprehension or generator expression you must use paren-free for/of syntax and only for/of. But could be confusing just on this basis.
I still think we're talking about reifying break as an exception here. That plus EIBTI mean we should do what Francois proposes, if we want to support this form, and require "throw break".
- Standardize a BreakException. Then breaking isn’t the loop’s responsibility, any more.
continue
as an early return seems less important, because you can use break as follows: mycoll.forEach { | x | block: { if (x < 0) { break block; // "continue" } // do more with x here } }
But this is ugly, and if we have "throw break" we may as well have "throw continue". Again I'm not on board, just noting the alternatives. Why stop half-way if we want to reify as exceptions (which again, to confirm, is what any mycoll.forEach break/continue hook-up would require)?
On Sat, Jan 14, 2012 at 2:05 PM, Brendan Eich <brendan at mozilla.org> wrote:
This reminds me of dherman's escape continuation proposal:
We did not promote it from Strawman to Harmony status.
It's likely no accident, but the direct Lisp equivalent to this (BLOCK and RETURN-FROM www.lispworks.com/documentation/HyperSpec/Body/s_block.htm)
have proven useful to me many times when I needed to either continue or break from a macro-defined looping construct.
I support the author being able to declare their own capacity to escape from block-lambda-driven "loops" using this mechanism. I like it much better than attempting to discriminate somehow between lambda-driven "loops" and other lambda-driven constructs, or some lambda-driven constructs invisibly handling break/continue themselves. Plus, it's very often useful to escape multiple levels of loops, which is not solved by extending break/continue, but is solved by return-to-label.
Tab Atkins Jr. <mailto:jackalmage at gmail.com> January 14, 2012 5:01 PM
It's likely no accident, but the direct Lisp equivalent to this (BLOCK and RETURN-FROM www.lispworks.com/documentation/HyperSpec/Body/s_block.htm) have proven useful to me many times when I needed to either continue or break from a macro-defined looping construct.
You: preacher; me: choir ;-).
I support the author being able to declare their own capacity to escape from block-lambda-driven "loops" using this mechanism. I like it much better than attempting to discriminate somehow between lambda-driven "loops" and other lambda-driven constructs, or some lambda-driven constructs invisibly handling break/continue themselves.
Please be clear so we can throw stones or flowers appropriately: you mean by "attempting to discriminate" any reification of break and continue as exceptions?
Plus, it's very often useful to escape multiple levels of loops, which is not solved by extending break/continue,
JS has had break/continue to label (a la Java) since ES3. (Why don't more people know about this? Have to talk to PR... :-)
but is solved by return-to-label.
This is not a good argument by itself, given break/continue-to-label. JS long ago passed up the chance to generalize those special forms, so they are with us and used on the web.
Adding escape continuations is another case of warty + shiny ~~~> shiny
promised land hope as the evolutionary game we have to play on the web. Still, I'm in favor of escape continuations but I do not want to bundle them with block-lambdas. One mountain to climb at a time.
On Jan 14, 2012, at 6:46 PM, Brendan Eich wrote:
Adding escape continuations is another case of warty + shiny ~~~> shiny promised land hope as the evolutionary game we have to play on the web. Still, I'm in favor of escape continuations but I do not want to bundle them with block-lambdas. One mountain to climb at a time.
With block lambdas that support the return statement we already have escape continuations:
function callWithEscapeContinuation(blockWithOneArg) { let escapeContinuation = {|value| return value}; return blockWithOneArg(escapeContinuation); }
result = callWithEscapeContinuation( {|escape| ... if (cond) escape(); ... }; ...
I used the Smalltalk equivalent [:v| ^v] all over the place in the implementation of the Smalltalk resumption-based exception handling system. In fact, with block lambdas and try-finally (not no catch). I could replicate the full Smalltalk-style EHS in JavaScript. (Not to argue that resumable exceptions are really that good of an idea).
Allen Wirfs-Brock <mailto:allen at wirfs-brock.com> January 14, 2012 9:50 PM
With block lambdas that support the return statement we already have escape continuations:
Right you are, but simulating "early return" in a block-lambda requires the caller to pass down a {|v| return v} block -- a bit of tedium that could take too many evenings, to borrow from Oscar Wilde.
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---
Brendan Eich wrote:
More when I reply to Axel in a bit.
I think there may have been a misunderstanding. My proposed solution amounts to syntactic sugar over the existing strawman and makes break/continue work intuitively with loopy block lambdas, and your reply to Axel appears to be against an exception-based version, but you seem to have (implicitly) tied them together.
Of course we want to uphold TCP, which constrains the design. What works here must work in a regular block statement as body of such a loopish structure.
Well, the LCP (Loop Correspondence Principle :) is maintained. So, you can change your loop construct from: for (var i=0; i<arr.length; ++i) { var o = arr[i]; if (...) break; }
To this: for arr.ForEach { |o| if (...) break; }
And it still works, without needing to throw exceptions or alter the existing library code. I believe TCP to be maintained everywhere it might apply, not least because there's a syntactic opt-in, but I may have missed what you're pointing out.
If the syntax conflicts disastrously with paren-free, then a suitable nearby syntax could be chosen. I'm not attached to the syntax; I'm simply offering an implementation strategy that's minimal, matches most requirements and doesn't appear to break any of the imposed rules; I don't know the other strawman proposals well enough to avoid conflicting syntax. It's a boolean toggle, though, so it should be easy to find a suitable syntax.
, Grant Husbands.
your 'for call {|| ...}' is great and simple solution for (unlabeled) break, but it is not so for continue. For either continue could have the redesigned meaning I sketched out in my last post, but then you need expression to specify; or you want the legacy meaning of "go on with the next step" which you can't achieve by simply skipping to end of the block and returning undefined - it works as continue in forEach and every, but not in some, for example.
Herby
-----Pôvodná správa---
it works as continue in forEach and every, but not in some
Sorry, mistake: it works as continue in forEach and some, but not in every
-----Pôvodná správa---
Herby Vojčík wrote:
your 'for call {|| ...}' is great and simple solution for (unlabeled) break, but it is not so for continue.
To clarify, it matches what continue currently does for other loops, which is what I was aiming for.
However, I do agree that early completion with a value is desirable and that continue could easily be adapted to accept a completion value for the block lambda. I didn't mean to exclude the possibility; rather, I just hadn't passed comment on it.
Do note that if it's added into my proposal as it stands, we only gain that functionality on lambda block usage that's labelled as loop-like. I don't know whether you find that acceptable, but it might be the only way that it can be widely accepted, due to TCP.
, Grant Husbands.
Grant Husbands <mailto:esdiscuss at grant.x43.net> January 15, 2012 3:51 AM Brendan Eich wrote:
More when I reply to Axel in a bit.
I think there may have been a misunderstanding. My proposed solution amounts to syntactic sugar over the existing strawman and makes break/continue work intuitively with loopy block lambdas, and your reply to Axel appears to be against an exception-based version, but you seem to have (implicitly) tied them together.
That's right, because I don't see how your proposal can work without exceptions. You write that it is yntactic sugar. Ok, what is the desugaring? How does
for arr.ForEach { |o| if (...) break; }
And it still works, without needing to throw exceptions or alter the existing library code.
translate into JS without a throw that the implementation of arr.forEach, which is not modfiied by this for-prefixed invocation, can catch?
Brendan Eich wrote:
I don't see how your proposal can work without exceptions. You write that it is syntactic sugar. Ok, what is the desugaring? How does
for arr.ForEach { |o| if (...) break; }
translate into JS without a throw that the implementation of arr.forEach, which is not modfiied by this for-prefixed invocation, can catch?
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.
, Grant Husbands.
Grant Husbands <mailto:esdiscuss at grant.x43.net> 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.
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.)
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 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.
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; }
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:indefinitelyiterate 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.
Other ideas?
-----Pôvodná správa---
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 Wirfs-Brock <mailto:allen at wirfs-brock.com> January 15, 2012 2:31 PM
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:indefinitelyiterate 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.
Herby Vojčík <mailto:herby at mailbox.sk> January 15, 2012 12:15 PM
-----Pôvodná správa----- From: Brendan Eich Sent: Sunday, January 15, 2012 8:56 PM To: Grant Husbands Cc: es-discuss at mozilla.org Subject: Re: Block Lambdas: break and continue
...
block-Other ideas?
/be
I sent an expression-less continue and break elaboration today. at 11:something CET. I would like to hear what is wrong with that, if something.
Your earlier message was way (way!) too long to get through today, and I stopped reading it when it proposed an ambiguous extension to continue, namely continue <expression>. The language already supports continue <label> where label is an identifier, which is also a valid expression.
Let's back up from trying to do something involving passing an expression via continue, since that is not needed for what Grant showed: a desugaring from for-prefix block-lambda to label-based block-lambda code.
It also seems to me, after some more thoughts, that block-lambdas with strict TCP and break/continue are mutually exclusive
Not so. The strawman requires and starts to specify TCP conformance for break, continue, return, and |this|.
and you must select one of them (if TCP is primary goal, break and continue must work the same independently of whether block-lambda is present or not
That's what the strawman proposes.
- it is what TCP is about, so even the idea of lambda-block-specific break/continue is breaking TCP). So what are we really looking for?
You haven't said what is mutually exclusive, and the proposal preserves TCP while supporting break and continue in block lambdas to outer loops/switches/labeled-statements, just as if the block-lambda were replaced by a block statement with the appropriate changes to surrounding code (TCP conformance).
Please try to show proof (a convincing argument) for claims like "mutually exclusive".
The main problem I would have with "for" as a prefix is semantic, not grammatical: I would always expect to loop over something iterable and not a loop implementation.
My impression: Let’s wait until we have a more powerful collection library (which I assume would use block lambdas extensively). Then it should become clear where/if people miss break and continue. A label is a reasonable work-around that should even survive the insertion of a “loop-ifying” keyword.
Could we call block lambdas just lambdas? The former seems a bit pleonastic.
Axel Rauschmayer <mailto:axel at rauschma.de> January 15, 2012 11:32 PM The main problem I would have with "for" as a prefix is semantic, not grammatical: I would always expect to loop over something iterable and not a loop implementation.
Yes, I agree with that and it's what I meant by head-less body (or is it body-less head?).
But the syntactic ambiguity is a problem too.
My impression: Let’s wait until we have a more powerful collection library (which I assume would use block lambdas extensively). Then it should become clear where/if people miss break and continue. A label is a reasonable work-around that should even survive the insertion of a “loop-ifying” keyword.
Agreed.
Could we call block lambdas just lambdas? The former seems a bit pleonastic.
Love that word. I didn't intend a pleonasm but I definitely felt some need to distinguish block-lambdas from Ruby blocks (while also giving them a hat tip), and from other lambda strawman proposals, especially
This looks orphaned in the wiki, so maybe there's no more chance of confusion. Happy to use "lambda" if I'm not stepping on someone else's toes.
On Jan 15, 2012, at 11:16 PM, Brendan Eich wrote:
I think you are forgetting the for(;;) loop. Paren-ful for loop:
yup, I thought the "Paren-free" reference was regarding paren-free calls. Paren-free in other context does indeed create ambiguities with my suggests. I had thought that paren-free outside of block-lambda calls was pretty much dead, but as you say it could be a future possibility so that needs to be factored into the discussion.
...
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.
I agree, this is exactly where I was until Grant's idea came along and seemed worth exploring. But if we want to future proof for full paren-free then I think we should simply defer any consideration of block-lambda friendly break/continue
...
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.
yup, missed that one
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:
Yes, I like that.
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.
I forget the "in" but it is better without it.
I see the main value of |continue| in block lambdas simply as an early exist from the lambda Just like with |return| in a normal function, it is sometimes clearer to do an explicitly early exist then to arrange the control flow to fall through to the end. Grant's pattern, used explicitly, actually seems like a good solution for cases where the block's continuation value is no needed: foo {|| exit: { ... if (...) break exit; ... } };
However, block lambdas are often evaluated for a value and none of the current continue/break based solutions include a way to provide an explicit continuation value. Here is an idea for that:
foo {||
exit: {
...
if (...) break exit with "early"; //[no LineTerminator here] before "with"
...
}
};
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"?
yes
... I repeat if you are willing to use a label, you don't need a prefix keyword. See Grant's desugarings.
Yes, I like that except for the completion value issue
However, block lambdas are often evaluated for a value and none of the current continue/break based solutions include a way to provide an explicit continuation value. Here is an idea for that:
foo {|| exit: { ... if (...) break exit with "early"; //[no LineTerminator here] before "with" ... }
};
+1
It might make sense to also introduce an “unlabeled” break-with that exits from the current (unlabeled) block.
With completion values as In ES1-5, not even depending on harmony:completion_reform, it might be enough to say:
foo {||
exit: {
...
if (...) {
"early";
break exit;
}
...
}
}
IINM this would work. Sugaring it using 'with' looks nice -- always tempting to re-use 'with'.
I have some angst about loss of a hunk of code running "break L" up against " with (E)\nS" where E is an expression and S is a statement and the two came from a bone-fide with statement later in the unmangled source.
On Jan 16, 2012, at 11:09 AM, Brendan Eich wrote:
With completion values as In ES1-5, not even depending on harmony:completion_reform, it might be enough to say:
You're right, according to ES 5 semantics this does provide "early" as the value of the labelled block.
foo {|| exit: { ... if (...) { "early"; break exit; } ... } }
IINM this would work. Sugaring it using 'with' looks nice -- always tempting to re-use 'with'.
and if fact it does work in the browsers I tested (without the block lambda), so I was wrong when I said there was no way to supply the completion value. But the sugaring is much more explicit.
I have some angst about loss of a hunk of code running "break L" up against " with (E)\nS" where E is an expression and S is a statement and the two came from a bone-fide with statement later in the unmangled source.
that's why I would require a [no LineTerminator here] before the with clause of a break statement
Allen Wirfs-Brock <mailto:allen at wirfs-brock.com> January 16, 2012 11:32 AM
On Jan 16, 2012, at 11:09 AM, Brendan Eich wrote:
I have some angst about loss of a hunk of code running "break L" up against " with (E)\nS" where E is an expression and S is a statement and the two came from a bone-fide with statement later in the unmangled source.
that's why I would require a [no LineTerminator here] before the with clause of a break statement
Right, got that -- it's why I wrote "unmangled". Sorry to be unclear. I'm supposing code of this form
foo {||
exit: {
...
if (...) {
"early";
break exit
}
...
}
}
...
with (O)
S;
got mangled into code of this form:
foo {||
exit: {
...
if (...) {
"early";
break exit
with (O)
S;
and all braces still matched -- and then a newline was lost. Yes, it's unlikely and minifiers must ensure semicolons are inserted, etc. Still a source of angst because the bad thing might go undetected.
If block-lambdas require version opt-in, then of course 'with' statements are early errors. That helps, but break L with E would be a general statement form, IIUC, so not just for block-lambdas. We'd have to say this new syntax requires version opt-in (something I'm arguing against in reply to Andreas R.).
Sorry to make a small hill out of a mole hill -- not my intention, but my angsty spider-sense is tingling when 'with' makes come-backs of this kind...
Allen Wirfs-Brock <mailto:allen at wirfs-brock.com> January 16, 2012 9:53 AM
On Jan 15, 2012, at 11:16 PM, Brendan Eich wrote:
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.
I agree, this is exactly where I was until Grant's idea came along and seemed worth exploring.
Just to be extremely clear (since I've failed at that, apparenty), I missed the desugaring Grant showed for too long. I agree it's worth exploring, which means exploring alternative syntax ideas to "for ".
So, kudos to Grant (and Axel, who IINM proposed "for " earlier and intended the exception-free desugared semantics).
Allen Wirfs-Brock <mailto:allen at wirfs-brock.com> January 15, 2012 2:31 PM
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.
Couple of alternative-syntax thoughts:
-
Empty label as optional prefix to CallWithBlockArguments, to make the block-lambda argument(s) breakable or continuable:
: arr.forEach {|o| if (...) break; ... }
The idea is that break or continue without label targets the nearest empty label or predefined target statement, whichever is closer. But I propose to narrow this to be a special prefix form only for CallWithBlockArguments.
In an expression context the leading colon requires parenthesization if used in the consequent of a ternary (?:) expression. In an object literal's PropertyAssignment you'd get a confusing ": :" juxtaposition. So it probably is better to require parentheses around empty-label-prefixed calls in all expressions.
The continue case would exit the containing block-lambda argument to the :-labeled CallWithBlockArguments instead of exiting the whole CallWithBlockArguments expression.
-
Variation on empty label: support "do" as a reserved-identifier label that is implicitly addressed by break; and continue; (no labels on the break and continue). A little Smalltalk homage and not so visually nasty and potentiailly confusing as empty label.
do: arr.forEach {|o| if (...) break; ... }
To complete the Smalltalk homage we would want this in expressions, and we'd also want do: to take a block-lambda directly, in addition to a CallWithBlockArguments.
This variation is future-hostile to leading-colon as statement- or expression-starting special forms (see strawman:return_to_label). I think that this is acceptable but I could be missing something.
Comments welcome.
On Jan 15, 2012, at 11:32 PM, Axel Rauschmayer wrote:
Could we call block lambdas just lambdas? The former seems a bit pleonastic.
Block lambdas is a proposal that combines block-based syntax with lambda-based semantics. There have been other proposals in the past which provide lambda semantics (i.e., TCP-respecting function literals) with different syntax. The syntax is a central part of the proposal, since it involves the completion value and the paren-free form. So I think the name is reasonable.
My $.02.
On Jan 16, 2012, at 11:09 AM, Brendan Eich wrote:
With completion values as In ES1-5, not even depending on harmony:completion_reform, it might be enough to say:
foo {|| exit: { ... if (...) { "early"; break exit; } ... } }
Yeah, I thought of that too, although it's unfortunately awfully obscure. It was also the motivation behind strawman:return_to_label
IINM this would work. Sugaring it using 'with' looks nice -- always tempting to re-use 'with'.
A clever idea, and more pleasant-looking than the syntax of my old return_to_label strawman. But it does have an ASI hazard:
break exit
with "early"
That might be what /be meant by:
On Jan 16, 2012, at 11:32 AM, Allen Wirfs-Brock wrote:
I have some angst about loss of a hunk of code running "break L" up against " with (E)\nS" where E is an expression and S is a statement and the two came from a bone-fide with statement later in the unmangled source.
that's why I would require a [no LineTerminator here] before the with clause of a break statement
Oops, should've read ahead before replying. Certainly it solves the problem of ambiguity, if not of ASI hazard.
Brendan Eich wrote:
- Variation on empty label: support "do" as a reserved-identifier label
do: arr.forEach {|o| if (...) break; ... }
This seems like a sound way of doing it, indeed (I omitted your first one because I prefer this one). It avoids the more egregious syntax conflicts and is indicative of being interesting to break/continue. Combined with the break-with and continue-with statements, elsewhere in this thread, it makes block lambdas better than anonymous functions currently are in nearly all cases, along with making it easier to replace loops with callback-based iteration.
To complete the Smalltalk homage we would want this in expressions, and we'd also want do: to take a block-lambda directly, in addition to a CallWithBlockArguments.
Would it call that block-lambda with no arguments?
This variation is future-hostile to leading-colon as statement- or expression-starting special forms (see <wiki.ecmascript.org /doku.php?id=strawman:return_to_label>). I think that this is acceptable but I could be missing something.
I think I'm misreading, but I'm not seeing how "do:" and "return :" conflict. If break-with and continue-with get specced, they would cover the same use case, anyway. I do agree that if ":" could start an expression and "do" could be used without braces, there would be a conflict; maybe there could be significant whitespace in that situation. I'm sure people more qualified than myself will cover this, though.
I don't know how the process works, but I'd be happy to assist in the creation of additional strawmen to cover these (potentially later) additions to block lambdas and other blocks, if consensus is reached. I don't want to jump the gun, though.
Oh, and I do agree that credit goes to Axel for the proposal.
Thanks, Grant.
Grant Husbands <mailto:esdiscuss at grant.x43.net> January 16, 2012 5:33 PM Brendan Eich wrote:
Variation on empty label: support "do" as a reserved-identifier label
do: arr.forEach {|o| if (...) break; ... }
This seems like a sound way of doing it, indeed (I omitted your first one because I prefer this one).
Me too. Had to throw the shorter one out to give it its due.
It avoids the more egregious syntax conflicts and is indicative of being interesting to break/continue. Combined with the break-with and continue-with statements, elsewhere in this thread, it makes block lambdas better than anonymous functions currently are in nearly all cases, along with making it easier to replace loops with callback-based iteration.
I will add strawman sections for these extensions to block_lamda_revival in a bit.
To complete the Smalltalk homage we would want this in expressions, and we'd also want do: to take a block-lambda directly, in addition to a CallWithBlockArguments.
Would it call that block-lambda with no arguments?
If the block-lambda takes no arguments, yes. But I'm thinking of CoffeeScript's do operator, which passes lexical references of the same name as the block-lambda's parameters:
coffeescript.org/#try:list %3D [1%2C 2%2C 3] for x in list do (x) -> alert x
list = [1, 2, 3] for x in list do (x) -> alert x
which for example translates to:
var list, x, _fn, _i, _len;
list = [1, 2, 3];
_fn = function(x) { return alert(x); }; for (_i = 0, _len = list.length; _i< _len; _i++) { x = list[_i]; _fn(x); }
Apologies for the CoffeeScript if it's not your thing, readers. Here's the block-lambda with do: version:
let list = [1, 2, 3] for (let x of list) { do: { |x| alert x } }
But of course, Harmony for-let-of and for-let-in loops provide a fresh binding per iteration, so this do: is not needed to capture each iteration's i value. Let's use old-style for:
let x; for (x = 1; x < 4; x++) { do: { |x| alert x } }
Here the single let x binding is prone to being captured with its final value, 4. The do: calls its block-lambda argument with each x value in turn, so (e.g.) the block-lambda can safely close over its parameter x and get each value in [1, 2, 3].
This variation is future-hostile to leading-colon as statement- or expression-starting special forms (see<wiki.ecmascript.org /doku.php?id=strawman:return_to_label>). I think that this is acceptable but I could be missing something.
I think I'm misreading, but I'm not seeing how "do:" and "return :" conflict.
You're right, I was mis-remembering the return-to-label details.
If break-with and continue-with get specced, they would cover the same use case, anyway.
Return-to-label has been fading for a while, it dates from an earlier lambda proposal era. I agree break/continue-with do the job. I'll talk to Allen about those a bit tomorrow.
I don't know how the process works, but I'd be happy to assist in the creation of additional strawmen to cover these (potentially later) additions to block lambdas and other blocks, if consensus is reached. I don't want to jump the gun, though.
I thought about new strawmen but still think it better to add do: and possibly break/continue-with to block-lambda revival to avoid too many little wiki pages. b/c-with, perhaps, deserve their own page but it's easy to split if necessary.
Thanks for the offer to help, I may take you up on it yet. And thanks for the discussion.
To complete the Smalltalk homage we would want this [the label "do:"] in expressions, and we'd also want do: to take a block-lambda directly, in addition to a CallWithBlockArguments.
Minor possibility of future-hostility: Should real keyword arguments ever make it to ECMAScript, labels such as "do:" seem an obvious choice.
On Jan 16, 2012, at 2:16 PM, Brendan Eich wrote:
... 2. Variation on empty label: support "do" as a reserved-identifier label that is implicitly addressed by break; and continue; (no labels on the break and continue). A little Smalltalk homage and not so visually nasty and potentiailly confusing as empty label.
do: arr.forEach {|o| if (...) break; ... }
do: arr.alternate ({|o| if (...) continue; if (...) break; ...}, {|o| if (...) continue; ...});
I don't see how this can support the most likely intended semantics: do:arr.alternate (:{|o| if (...) continue; if (...) break do); ...}, : {|o| if (...) continue; ...});
might, but ugly
Allen Wirfs-Brock wrote:
do: arr.alternate ({|o| if (...) continue; if (...) break; ...}, {|o| if (...) continue; ...});
I don't see how this can support the most likely intended semantics
I think others might have better answers, but it seems that the meaning of 'break' is to stop the whole statement, and the meaning of 'continue' is to skip the inner block and hence return to arr.alternate. I'm sorry for my woolly language, but it seems relatively equivalent to a for loop, in which 'break' stops the whole statement and 'continue' skips the inner block and hence returns to the looping code.
Perhaps what I'm saying is that I think "do:" is a label for the whole callexpression, covering all lambda-block parameters. As far as the specification goes, a continue that 'hits' the block lambda may well be best described as a local return.
Or I might be misreading.
, Grant Husbands.
Axel Rauschmayer <mailto:axel at rauschma.de> January 17, 2012 1:04 AM
Minor possibility of future-hostility: Should real keyword arguments ever make it to ECMAScript, labels such as "do:" seem an obvious choice.
No, JS doesn't allow reserved identifiers to be used as parameter names in any case.
Grant Husbands <mailto:esdiscuss at grant.x43.net> January 17, 2012 10:14 AM
I think others might have better answers, but it seems that the meaning of 'break' is to stop the whole statement, and the meaning of 'continue' is to skip the inner block and hence return to arr.alternate
That's what I was thinking too.
On Jan 17, 2012, at 10:14 AM, Grant Husbands wrote:
Allen Wirfs-Brock wrote:
do: arr.alternate ({|o| if (...) continue; if (...) break; ...}, {|o| if (...) continue; ...});
I don't see how this can support the most likely intended semantics
I think others might have better answers, but it seems that the meaning of 'break' is to stop the whole statement, and the meaning of 'continue' is to skip the inner block and hence return to arr.alternate. I'm sorry for my woolly language, but it seems relatively equivalent to a for loop, in which 'break' stops the whole statement and 'continue' skips the inner block and hence returns to the looping code.
Yes, that was the intended meaning I was trying to express. But what I was illustrating was that for this to work a continue wihout a target label and an equivalently located break without a target label need to unwind to different points in the enclosing nesting structure. This seems different (perhaps surprisingly so) from equivalent continue/breaks nested only within blocks and an IterationStatement. But that is the semantics that are need to fulfill my intent is this particular case. Maybe this generalizes to all use reasonable cases, but it something about it makes me feel a bit uncomfortable.
Perhaps what I'm saying is that I think "do:" is a label for the whole callexpression, covering all lambda-block parameters. As far as the specification goes, a continue that 'hits' the block lambda may well be best described as a local return.
Yes, this seems about right. But can this behavior for break/continue be explained in a way that doesn't seems arbitrarily different from break/continue in the context of IterationStatements and regular blocks.
Perhaps we can describe a general transformation that converts an IterationStatement to/from an equivalent call to an iteration function with block lambda arguments.
Allen Wirfs-Brock <mailto:allen at wirfs-brock.com> January 17, 2012 7:56 PM
On Jan 17, 2012, at 10:14 AM, Grant Husbands wrote:
Allen Wirfs-Brock wrote:
do: arr.alternate ({|o| if (...) continue; if (...) break; ...}, {|o| if (...) continue; ...});
I don't see how this can support the most likely intended semantics
I think others might have better answers, but it seems that the meaning of 'break' is to stop the whole statement, and the meaning of 'continue' is to skip the inner block and hence return to arr.alternate. I'm sorry for my woolly language, but it seems relatively equivalent to a for loop, in which 'break' stops the whole statement and 'continue' skips the inner block and hence returns to the looping code.
Yes, that was the intended meaning I was trying to express. But what I was illustrating was that for this to work a continue wihout a target label and an equivalently located break without a target label need to unwind to different points in the enclosing nesting structure. This seems different (perhaps surprisingly so) from equivalent continue/breaks nested only within blocks and an /IterationStatement/. But that is the semantics that are need to fulfill my intent is this particular case. Maybe this generalizes to all use reasonable cases, but it something about it makes me feel a bit uncomfortable.
Agreed, so I'm checking my impulse to extend strawman:block_lambda_revival and aiming at separated strawmen for do: and break/continue-with. Not sure at this point when I'll get to them, more discussion welcome.
Perhaps what I'm saying is that I think "do:" is a label for the whole callexpression, covering all lambda-block parameters. As far as the specification goes, a continue that 'hits' the block lambda may well be best described as a local return.
Yes, this seems about right. But can this behavior for break/continue be explained in a way that doesn't seems arbitrarily different from break/continue in the context of /IterationStatements/ and regular blocks.
Perhaps we can describe a general transformation that converts an /IterationStatement/ to/from an equivalent call to an iteration function with block lambda arguments.
This reminds me of
et seq.
Brendan Eich wrote:
Allen Wirfs-Brock <mailto:allen at wirfs-brock.com> January 17, 2012 7:56 PM
On Jan 17, 2012, at 10:14 AM, Grant Husbands wrote:
I think others might have better answers, but it seems that the meaning of 'break' is to stop the whole statement, and the meaning of 'continue' is to skip the inner block and hence return to arr.alternate. I'm sorry for my woolly language, but it seems relatively equivalent to a for loop, in which 'break' stops the whole statement and 'continue' skips the inner block and hence returns to the looping code.
Yes, that was the intended meaning I was trying to express. But what I was illustrating was that for this to work a continue wihout a target label and an equivalently located break without a target label need to unwind to different points in the enclosing nesting structure. This seems different (perhaps surprisingly so) from equivalent continue/breaks nested only within blocks and an /IterationStatement/. But that is the semantics that are need to fulfill my intent is this particular case. Maybe this generalizes to all use reasonable cases, but it something about it makes me feel a bit uncomfortable.
Agreed, so I'm checking my impulse to extend strawman:block_lambda_revival and aiming at separated strawmen for do: and break/continue-with. Not sure at this point when I'll get to them, more discussion welcome.
See below.
Perhaps what I'm saying is that I think "do:" is a label for the whole callexpression, covering all lambda-block parameters. As far as the specification goes, a continue that 'hits' the block lambda may well be best described as a local return.
Yes, this seems about right. But can this behavior for break/continue be explained in a way that doesn't seems arbitrarily different from break/continue in the context of /IterationStatements/ and regular blocks.
Maybe you can, after all, read my post on the topic from few days ago. It is not that long, it does break/continue with specifying value, and it gives them concise meaning (which is easily updateable for case of "label: { ....; break label; ... }" as well as "for call() {|| ...})".
Brendan Eich wrote:
Herby Vojčík <mailto:herby at mailbox.sk> January 18, 2012 2:34 AM Brendan Eich wrote:
Perhaps what I'm saying is that I think "do:" is a label for the whole callexpression, covering all lambda-block parameters. As far as the specification goes, a continue that 'hits' the block lambda may well be best described as a local return.
Yes, this seems about right. But can this behavior for break/continue be explained in a way that doesn't seems arbitrarily different from break/continue in the context of /IterationStatements/ and regular blocks.
Maybe you can, after all, read my post on the topic from few days ago. It is not that long, it does break/continue with specifying value, and it gives them concise meaning (which is easily updateable for case of "label: { ....; break label; ... }" as well as "for call() {|| ...})".
Priivate reply here.
Using | delimiters around an optional expression after break or continue is a pipe too far. Just IMHO. We can bikeshed the syntax but Allen's 'with' retasking seems better.
Why not? It is not about syntax in the first place. I used | bars to "hint" that value-returning continue semantics (and break, too, if I think about it) is useful primarily in context of lambda-blocks. It may be with (but you sounded you do not want to have continue returning value at all).
If module-opt-in or whatever-new-opt-in strategy fails and feature-by-feature opt-in would come to place, I would see good use of with there, by saying
with newObjectLiteral, destructuringAssignment
construct to opt-in features. Otherwise, with is ok.
Also (I'm in a glass house on this due to volume of HTML overciting and broken images my new mail user-agent has sent, so please do not take this as me throwing a stone) your post still should be shorter to be more persuasive, or just digestible.
I have sent lot of mali which tried to be short and in medias res. I was misunderstood and always got a short reply. So I wanted to explain more. It's hard to balance.
Just above you reply to Allen's cited text, not mine. Earlier I said I'd write a separate strawman for block-related ideas (do: and break/continue with value). So I'm not sure what you are asking me to do, other than consider |-delimiting instead of Allen's break/continue-with. Is that it?
As I said, it's not about syntax. I have replied primarily to the fact that you (plural you) have searched for concise break/continue semantics in context of old use and at the same time in the calls with block lambdas, I pointed out that I included it in my post, which is a bit long, but it is there, all; with the try to explain why, not only what and how.
Block lambdas have been a hot topic, recently, but there's a point of significant divergence between Ruby (which appears to be the inspiration) and the proposed solution, in the handling of continue (called 'next', in Ruby) and 'break'.
To whit: In Ruby, 'next' will end the current run (iteration) of the block, and 'break' will (somehow) terminate the method lexically connected with the block. It can be claimed that this is more intuitive than the current proposal, which aims to make 'break' and 'continue' propagate through block lambdas in the same way 'return' would.
Ruby does also support syntactic loops and the same keywords therein and so directly violates Tennent's Correspondence Principle, even though such has been touted as a core reason for the construct. Instead, I believe it reasonable to invoke intuition in this matter. It is intuitive for 'return' to return a value from the lexically enclosing method and it is intuitive for 'continue' to commence the next iteration of the current loop, however that loop is constructed.
Note that the label-based break/continue could still have the desired effect, if the proposal was updated to be more like Ruby's blocks.
I don't have a strong opinion on the subject, but I hadn't noticed the above being discussed, elsewhere, and thought it worth raising. If there is a better place for me to raise this, please let me know where and accept my apologies.
, Grant Husbands.