wren icon indicating copy to clipboard operation
wren copied to clipboard

[RFC] Multi-level break and continue.

Open PureFox48 opened this issue 3 years ago • 53 comments

At present break and continue only break out of or continue from the end of the immediately enclosing loop (for or while).

If you want to break out of two nested loops from the inner one you have to do something like this:

for (i in 1..5) {
    var outer = true
    for (j in 1..5) {
        if (someCondtion(i, j)) {
            outer = false   // want to break out of outer loop here
            break
        }
    }
    if (!outer) break
}

This isn't too bad but if you have 3, 4 or even more nested loops, it can lead to some messy and hard to understand code.

I'd like to suggest that break and continue be allowed to accept an optional 'levels' argument which would tell them how many nested enclosing loops are to be broken out of or continued from the end of.

PHP has a similar construct - see here and here.

As the two keywords would be treated similarly, I'll just talk about break in the rest of this post.

With this construct in place, the above example would become:

for (i in 1..5) {
    for (j in 1..5) {
        if (someCondtion(i, j)) break 2   // break from the outer loop
    }
}

break 1 would be allowed (for contradistinction purposes) but the 1 would be ignored.

However, break n would be a syntax error if there were less than n enclosing loops.

I'll admit immediately that I don't even know whether something like this would be feasible for Wren's single pass compiler but I'd appreciate any comments on both that and the general idea.

PureFox48 avatar Apr 06 '21 15:04 PureFox48

This would be a cool feature, I've certainly had my fair share of times where I wished I could multi-break. But it's also fairly easy to work around, so not something I would prioritise.

avivbeeri avatar Apr 06 '21 15:04 avivbeeri

But it's also fairly easy to work around, so not something I would prioritise.

TBH, I was overjoyed to get continue at all so I'm with you on that. But to have multi-level as well would be the icing on the cake :)

PureFox48 avatar Apr 06 '21 16:04 PureFox48

I also thought about something like that, but the writing is tricky. I didn't though about adding a number, it is better than my ideas so far. A stupid idea since I'm starting to code ^ lookup operator, maybe mix both to avoid a plain random number and use ^break ^continue instead?

mhermier avatar Apr 06 '21 16:04 mhermier

Both Java, JavaScript and Rust (and possibly others) allow to break into a label, which is more clear:

outer: for (i in 1..5) {
    for (j in 1..5) {
        if (someCondtion(i, j)) {
            break outer
        }
    }
}

Although PHP uses numbers, I prefer the label approach.

ChayimFriedman2 avatar Apr 06 '21 16:04 ChayimFriedman2

Personally, I'd prefer a number as I find that more intuitive than a symbol or a label though I'm certainly open to other ideas.

PureFox48 avatar Apr 06 '21 16:04 PureFox48

@ChayimFriedman2

Can you think of any other areas where labels might be useful in Wren as it would be quite a big change to add them just for this particular feature.

I think we can probably all agree that we don't want goto added to the language.

PureFox48 avatar Apr 06 '21 16:04 PureFox48

Of course not. I don't think labels are useful for anything else, but I think they're useful enough for this 😃

ChayimFriedman2 avatar Apr 06 '21 16:04 ChayimFriedman2

May https://github.com/wren-lang/wren/pull/962

be handy

You could set an attribute before your loop #break = 2 #continue = 2

and then when you use break or continue it will break 2 or continue 2

clsource avatar Apr 06 '21 18:04 clsource

note that attributes only exist on methods + classes, not statements/expressions.

ruby0x1 avatar Apr 06 '21 18:04 ruby0x1

I'll say it again, but why not something like the ^ operator I try to introduce? Ex ^^break would exit the third level of loop, ^continue would restart second level of looping. It would unify somehow the meaning of ^ left operator.

mhermier avatar Apr 07 '21 09:04 mhermier

I'm not fond of that. It looks quite ugly to me, and isn't in keeping with the rest of Wren's syntax.

avivbeeri avatar Apr 07 '21 09:04 avivbeeri

None of the solution doesn't go well with the syntax. Adding labels introduce a loophole where people will want to have goto, and fells not really integrated since it requires a new syntax. Adding a random number next to it, is almost the definition of not integrated. There is no clean way to solve it. But since the concept is near to upvalue lookup, it sounds to me to be a more logical/integrated solution to unify the syntax. But it maybe it's only me.

mhermier avatar Apr 07 '21 09:04 mhermier

Well, I do think that using ^ is a reasonable solution for dealing with nested scopes even if it's not very pretty.

However, we're talking here about nested loops - not really the same thing at all - and attaching the ^'s to a keyword rather than an identifier.

PHP isn't a language which I often turn to for inspiration but, on this occasion, I think they got it right with using numbers for looping levels and that this would be a simple elegant solution for Wren as well.

Although I don't think labelled break and continue are bad (I've used them in Java and Kotlin), it does mean that you have to think up suitable names for the labels each time (no such problem with numbers). Moreover, I don't really think this feature alone would justify adding the 'label' concept to Wren in any case.

PureFox48 avatar Apr 07 '21 09:04 PureFox48

The thing is that all these solutions are working solution, and they are just that. Labelling a loop, is a cheap solution to try to name a switch loop. Adding a number, is the same thing as ^ with a number that pop from nowhere...

The more elegant way would be to be able to properly name those loop but AFAIK it does not exist in literature, but maybe there is prior art somewhere.

mhermier avatar Apr 07 '21 10:04 mhermier

I don't understand why you're saying that the number would "pop from nowhere...". Acceptable values for the number would be 1..n where n is the number of enclosing loops at the point of usage - any other number would be an error.

I guess it's a matter of taste but personally I find break 2 clearer than ^break.

Also, if your ^ idea were adopted for nested scopes, people would be mentally looking for the next enclosing scope which might not be the same as the next enclosing loop.

PureFox48 avatar Apr 07 '21 10:04 PureFox48

Putting a number next to a keyboard, is what I call adding a number from nowhere. It is never used elsewhere than to provide a solution to that particular problem. Likewise for the ^ operator. But imagine I use the same syntax to say: I want to use variable foo from 2 scope once using foo 2. I find it really confusing.

mhermier avatar Apr 07 '21 10:04 mhermier

Well, there is a precedent in Wren for a 'jump' keyword to take an optional argument, namely return.

In the latter case, the argument can be any expression. In the case of break and continue, it would just be a numeric constant within 1..n.

You couldn't use the number syntax for resolving the use of the same identifier in different scopes but, to me, that's a completely different problem anyway.

PureFox48 avatar Apr 07 '21 10:04 PureFox48

Incidentally, I've just remembered that PHP is not the only language which uses numbers for multi-level break and continue.

Bash scripting has a similar feature.

PureFox48 avatar Apr 07 '21 10:04 PureFox48

True, though historically the return without a parameter is at least in C a language abuse/simplification/modernisation. Some old compiler requires parenthesis after the return and returning void is therefore return().

Technically the numbered and the ^ notations are equivalent. Though I tends to prefer operator, as it usually opens for more reuse and composition. But maybe it is probably a bias of me.

mhermier avatar Apr 07 '21 10:04 mhermier

Likewise I could use a keyword and do something like: lookup var_name 2 = 42 but I don't fell like it is as integrated as ^^var_name = 42. So the same logic applies for me there.

mhermier avatar Apr 07 '21 11:04 mhermier

Can break and continue be operators to numbers?

2 break 3 continue

or methods?

2.break 3.continue

clsource avatar Apr 07 '21 11:04 clsource

@mhermier

As far as #955 is concerned, I'd stick with your ^ idea. I can't think of anything better.

As far as this proposal is concerned, I'm sticking with my 'numbers' :)

@clsource

It seems to me that if we're to do anything about this problem then the solution must be part of the language syntax itself.

PureFox48 avatar Apr 07 '21 11:04 PureFox48

@clsource while it is possible I don't think it is desirable as this for various reasons, like compiler performance or opening the door to named operators. I don't say we might want to add them at some point to have units, but it is an even bigger beast to deal with.

mhermier avatar Apr 07 '21 12:04 mhermier

how about using the statment like this

  • break break
  • continue continue

I dont know how popular is having more than 3 levels of nesting in loops :)

clsource avatar Apr 07 '21 12:04 clsource

How is that better than break 2 or continue 2 ?

There are quite a few algorithms which require more than 2 nested loops. I come across them all the time in my RC efforts.

PureFox48 avatar Apr 07 '21 12:04 PureFox48

Although break 2 seems shorter I find that passing a param is like a function rather than a statement. By repeating the same statement x amount of times in the same line it's explicit that we are breaking two or three levels and does not seems like a function.

But if we must use that syntax

Another ideas is using as for tagging loops. Here the concepts of atoms can be used or a simple string

They are different to labels since they only can be used as a way to identify loops and not blocks of code.


while (expr) as "tag" {
  while (expr2) as :inner: {
        if (condition) {
            break "tag"
       }
  }
}

clsource avatar Apr 07 '21 13:04 clsource

Well, I'll grant you that break break is explicit but it's very verbose indeed for more than 2 levels.

Actually, I quite like your tag idea. as will become a keyword (for import aliasing) in v0.4.0 so that's not a problem.

I prefer it to labels as it follows the loop header rather than precedes it which is less obtrusive.

So, I think we now have 4 viable propositions here to choose from.

PureFox48 avatar Apr 07 '21 13:04 PureFox48

A further thought on your tag idea.

It might be better if the tag were an identifier rather than a string which would then chime better with how as is going to be used in the import statement. This identifier would have the same scope as it's associated loop.

while (expr) as outer {
  while (expr2) {
    if (condition) break outer
  }
}

PureFox48 avatar Apr 07 '21 13:04 PureFox48

Interesting solution to reflect a little bit more since it might make precedence in the language (we need to think about other use). Name collision should be though. The only biggest cons I see is that it not easily compatible with live coding. Otherwise it is viable as it satisfy most criteria.

mhermier avatar Apr 07 '21 13:04 mhermier

If the tag were an identifier, then I don't think it would matter if you re-declared the same identifier in the same scope even though it would be silly to do so.

As far as break and continue were concerned, they would just look back though the loop tags until they found the one (or the first instance of it) they needed and 'error out' if it wasn't present.

PureFox48 avatar Apr 07 '21 14:04 PureFox48