Block Lambdas: break and continue

François REMY fremycompany_pub at yahoo.fr
Sat Jan 14 13:01:55 PST 2012


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 
  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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120114/7d967c87/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: postbox-contact.jpg
Type: image/jpeg
Size: 1080 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120114/7d967c87/attachment-0005.jpg>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: postbox-contact.jpg
Type: image/jpeg
Size: 1254 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120114/7d967c87/attachment-0006.jpg>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: postbox-contact.jpg
Type: image/jpeg
Size: 1222 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120114/7d967c87/attachment-0007.jpg>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: postbox-contact.jpg
Type: image/jpeg
Size: 1290 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120114/7d967c87/attachment-0008.jpg>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: compose-unknown-contact.jpg
Type: image/jpeg
Size: 770 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120114/7d967c87/attachment-0009.jpg>


More information about the es-discuss mailing list