wren
wren copied to clipboard
[RFC] Multi-level break and continue.
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.
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.
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 :)
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?
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.
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.
@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.
Of course not. I don't think labels are useful for anything else, but I think they're useful enough for this 😃
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
note that attributes only exist on methods + classes, not statements/expressions.
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.
I'm not fond of that. It looks quite ugly to me, and isn't in keeping with the rest of Wren's syntax.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Can break and continue be operators to numbers?
2 break 3 continue
or methods?
2.break 3.continue
@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.
@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.
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 :)
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.
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"
}
}
}
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.
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
}
}
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.
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.