continue and break
samuelgoto opened this issue · 1 comments
This is a collection of alternatives on how to handle break and continue.
Read this first
There is a lot of context/background and ideas considered in these threads. Please read them first.
- block lambdas and break
- http://wirfs-brock.com/allen/files/jshistory/continue-break-lambda.pdf
- how ruby deals with break/next/return in blocks
- how kotlin deals with break/continue/return in lambdas
Introduction
continue and break are interesting cases because there are two valid uses:
for (let i = 0; i < 10; i++) {
unless (i == 5) {
// You'd expect the continue to apply to the
// lexical for, not to the unless
continue;
}
}Whereas:
for (let i = 0; i < 10; i++) {
foreach (array) {
if (::item == 5) {
// You'd expect the continue here to apply to
// the foreach, not the lexical for.
continue;
}
}
}Here are some options to be explored / compared.
Alternatives
Disallow
Like kotlin, In this formulation, we would disallow break and continue inside block params. This would lead into block params not supporting iterations or early returns. I believe this wouldn't corner ourselves, so could be a smart way to sequence things.
Inline
Inspired by kotlin's inline functions, in this formulation, the semantics of break and continue would be determined evaluating the result of inlining (metaphorically, not literally wrt performance) the entire function call (e.g. like a C macro works). For example:
// ... for example ...
function unless(expr, block) {
if (!expr) {
block();
}
}
for (let i = 0; ) {
unless (i % 2 == 0) {
continue;
}
}
// ... gets interpreted as ...
for (let i = 0; ) {
if (!(i % 2 == 0)) {
continue;
}
}
// ... i.e. the continue applies to the for ...Whereas
// ... whereas foreach ...
function foreach(collection, block) {
for (let v of collection) {
block.call({item: v});
}
}
foreach ([1, 2, 3]) {
if (::item % 2 == 0) {
continue;
}
}
// ... gets inline as ...
let collection = [1, 2, 3];
for (let v of collection) {
this.item = v;
if (::item % 2 == 0) {
// gets applied to the internal for-loop
// created inside for-each
continue;
}
}Modifier
Inspired by kotlin's inline functions, in this formulation, we could make the semantics switch based on the declaraction of the function.
inline function unless(expr, block) {
// ... breaks and continues bound lexically ...
}
function foreach (collection) {
// ... breaks and continues bound locally ...
}Call modifier
inspired by java's for, in this formulation the distinction in behavior would be done at all site. For example:
loop foreach () {
// ... breaks and continues bound lexically ...
}
foreach () {
// ... breaks and continue bound locally ...
}Standard Exceptions
One interesting approach here is to make continue and break throw a special standard Exception (say, ContinueException and BreakException), which can then be re-thrown or not (and understood by the lexical blocks).
foreach (array) {
continue;
}
// ... is sugar for ...
foreach (array, function() {
throw new ContinueException();
});
// ... which can be caught and re-thrown depending on context ...Comes with challenges.
Labels
Another interesting approach here is to make continue and break behave lexically, but explicit labels to mean the local ones. For examples:
unless (...) {
continue; // lexical scope
}
foreach (...) {
continue foreach; // local scope
}This pulls the responsibility to the user to know the distinction, which I'm not sure if the right trade-off.
NOTE(goto): kotlin is planning to include continue/break but for inline functions only, meaning that you can't do a foreach with continue/break.
NOTE(goto): would love to hear about alternatives here.
Oh, and here's a couple fun ones that complicate everything: yield and await. They suspend the execution context, making standard functions impossible to use without implementing stackful coroutines under the hood for DSLs (which I expect to be a no-go, since generators are stackless coroutines).