Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow "continue" and "break" within comprehensions, behaving as filters. #372

Closed
weepy opened this issue May 14, 2010 · 13 comments
Closed

Comments

@weepy
Copy link

weepy commented May 14, 2010

x: ->
  t: for move in moves
    continue if x
    move  

gives

var x;
x = function x() {
  var _a, _b, _c, _d, move, t;
  t =   _a = []; _c = moves;
  for (_b = 0, _d = _c.length; _b < _d; _b++) {
    move = _c[_b];
    if (x) {
      continue;
    }
    move;
  }
_a;
};

Note the final _a

@weepy
Copy link
Author

weepy commented May 14, 2010

I suppose it's because you shouldn't be able to assign a for loop with a continue.
I would be quite clever if we could recompile the continue into the equivalent of the when. I find that the when gets quite difficult to read.

@weepy
Copy link
Author

weepy commented May 14, 2010

I'm sure there was a very good reason why

x: for i in A
   a()
   continue if c2
   b()
   continue if c3
   c()

Doesn't compile to:

x = (function() {
  _e = []; _g = A;
  for (_f = 0, _h = _g.length; _f < _h; _f++) {
    i = _g[_f];
    a()
    if(c2)
      continue 
    b()
    if(c3) 
      continue
    _e.push((function() {
      return c();
    })());
  }
  return _e;
})();

But I can't remember it! Can someone enlighten me?

@jashkenas
Copy link
Owner

I don't think we really want to make continue part of a comprehension ... the three nodes: return, continue, and break, are "pure statements" because they lose all of their meaning when you wrap them in a closure. You can't inject a function in between them and their loop, without destroying the desired behavior, in the general case.

So, for your example, use a "when" clause, or just build up the results yourself:

t: move for move in moves when not x

# Or:

t: []
for move in moves
  t.push move unless x

@zmthy
Copy link
Collaborator

zmthy commented May 15, 2010

I agree with weepy here. Why don't we just leave out adding any value to the comprehension if the loop hits a statement?
for move, i in moves
if i % 2 is 0 then move else continue
This allows us to halve the size of the array rather than fill it with null values or the like.

@weepy
Copy link
Author

weepy commented May 15, 2010

It's certainly more expressive if we allow continue and break.
I find myself not using the when clauses as they are less powerful and end up looking quite messy (too much logic on one line)

@jashkenas
Copy link
Owner

Good idea. Reopening as an "enhancement"...

Edit:
Tesco and I talked about it a bit on #coffeescript, but the main impossibility here is that we use closure wrappers in order to convert statements into expressions. You can have a for loop with a large body, and somewhere in the body, the continue is present as part of a larger expression. When the expression is closure-wrapped for JavaScript, the continue is inside of it, and looses its meaning and placement in the for loop. For example...

A verbose way to get a list of even numbers, with this proposal, would be:

result: for x in list
  continue if x % 2 isnt 0
  x

If we naively stop treating "break" and "continue" as pure statements, we get:

var _a, _b, _c, _d, result, x;
result = (function() {
  _a = []; _c = list;
  for (_b = 0, _d = _c.length; _b < _d; _b++) {
    x = _c[_b];
    _a.push((function() {
      if (x % 2 !== 0) {
        continue;
      }
      return x;
    })());
  }
  return _a;
})();

Which destroys the "continue". CoffeeScript used to have a custom strategy for converting each statement node into an expression, but using closures across the board both simplified matters and made things more reliable... So I'm not sure how feasible this ticket really is.

@weepy
Copy link
Author

weepy commented May 15, 2010

I'm sorry I'm probably missing something, but I can't quite see what the inner closure provides. It seems to overcomplicate what should be a fairly simple loop.
E.g. currently the evens example compiles a loop like:

var _a, _b, _c, _d, result, x;
result = _a = []; _c = list;
for (_b = 0, _d = _c.length; _b < _d; _b++) {
  x = _c[_b];
  if (x % 2 !== 0) {
    continue;
  }
  x;
}
_a;

All we need is to change the last line of the loop to _a.push(x) and we're done?

@weepy
Copy link
Author

weepy commented Jun 4, 2010

any further thoughts on this ? Do you think using a custom strategy for looping expressions is a bad idea

Edit: Sorry i meant to add that I have been writing quite alot of CoffeeScript recently and personally I have found that the loops part of CS is quite complex and rather unintuitive Im sorry to say.

@hen-x
Copy link

hen-x commented Jun 4, 2010

I think that either break and continue should work as expected inside comprehensions/expressions, or they should be a compilation error. The current situation is a little confusing and counterintuitive.

We don't necessarily have to give up the loop body closures, either. Jashkenas' example above could be compiled to something like this:

var _a, _b, _c, _d, _e, result, x;
var _continue = {};
result = (function() {
  _a = []; _c = list;
  for (_b = 0, _d = _c.length; _b < _d; _b++) {
    x = _c[_b];
    _e = (function() {
      if (x % 2 !== 0) {
        return _continue;
      }
      return x;
    })();
    if (_e === _continue) {
        continue;
    }
    _a.push(_e);
  }
  return _a;
})();

@jashkenas
Copy link
Owner

sethaurus: I'm at a loss as to how we could start a patch to make this work... Given that:

  1. You can have an arbitrarily deep expression as the body of a loop.
  2. You can use "break" or "continue" as deep within the expression as you like.
  3. In between the loop and the "break", we need to insert a closure wrapper.

I don't see how we can preserve the meaning of the "break", without resorting to exception-throwing tomfoolery, or multiple levels of "return" with special objects getting passed out, and checked for.

It would be lovely if the semantics of JavaScript allowed you to use "break" across a function boundary, working with an external loop, and would fix the current JS nastiness with StopIteration and breaking out of a .forEach() as well, but I'm afraid that's not something we can do at compile time.

@weepy
Copy link
Author

weepy commented Jun 12, 2010

There must be a way get loops and comprehensions to work without closures?

@jashkenas
Copy link
Owner

Still taking another couple looks at this, but I don't see a way to improve the current situation. sethaurus's example is great, but checking for the _continue would break any potential returns, leading to the addition of _return checking, and also _break checking, in the worst possible scenario.

Closing the ticket, but will happily re-open it if someone has a complete proposal or patch.

@doublerebel
Copy link

👍 for break in the form of take while/take until as described in #3158.
I think linked lists are a great example of why this is useful as described in #3499.
This is rather related to #899/#869, but then I saw #1669, where continue is actually implemented.

I was creating a comprehension in this form (oversimplified example):

list = for x in [1,2,3]
  if x is 1
    x
  else if x is 2
    x

I was surprised to find Coffee inserting a void(0) at the end. I would argue in this case the expected behavior would be an implicit continue, rather than an implicit void(0), and a void(0)/undefined/null should be explicitly specified if needed.

I was able to solve it by using continue as specified in #1669:

list = for x in [1,2,3]
  if x is 1
    x
  else if x is 2
    x
  else continue

This is counter-intuitive IMHO, but I'm sure swapping the behavior would be a breaking change for dependent code. Therefore I think the clearest path forward is take while/take until.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants