Block Lambdas: break and continue

Herby Vojčík herby at mailbox.sk
Sat Jan 14 13:46:32 PST 2012


=== 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----- 
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.



















-- 
Dr. Axel Rauschmayer
axel at rauschma.de

home: rauschma.de
twitter: twitter.com/rauschma
blog: 2ality.com


















Brendan Eich
January 13, 2012 8:54 PM


Grant Husbands
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.

/be

Grant Husbands
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) 
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.

Regards,
Grant Husbands.
_______________________________________________
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
_______________________________________________ es-discuss mailing list 
es-discuss at mozilla.org https://mail.mozilla.org/listinfo/es-discuss

Brendan Eich
January 14, 2012 12:51 PM


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.





















-- 
Dr. Axel Rauschmayer
axel at rauschma.de

home: rauschma.de
twitter: twitter.com/rauschma
blog: 2ality.com


















Brendan Eich
January 13, 2012 8:54 PM


Grant Husbands
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.

/be

Grant Husbands
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) 
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.

Regards,
Grant Husbands.
_______________________________________________
es-discuss mailing list
es-discuss at mozilla.org
https://mail.mozilla.org/listinfo/es-discuss


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|;

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----- 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


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.





















-- 
Dr. Axel Rauschmayer
axel at rauschma.de

home: rauschma.de
twitter: twitter.com/rauschma
blog: 2ality.com



















More information about the es-discuss mailing list