dotnet / csharplang

The official repo for the design of the C# programming language
11.52k stars 1.03k forks source link

[Proposal] Labeled loops like in Java #1597

Closed AustinBryan closed 2 years ago

AustinBryan commented 6 years ago

Currently the cleanest way to break out of a nested loop is this:

for (int i = 0; i < 5; i++)
    for (int j = 0; j < 3; j++)
        if (i * j == 10) 
            goto outer;

outer: {}

If all we wanted to do was break out of the loops, we have to do outer: {}, which is sort've clunky. What we could do instead is use labeled loops like Java, so we can do this:

outer: for (int i = 0; i < 5; i++)
    inner: for (int j = 0; j < 3; j++)
        if (i * j == 10) 
            break outer;

And instead of:

int j = 0;

for (int i = 0; i < 5; i++)
    inner: for (; j < 3; j++)
        if (i * j == 10) 
            goto inner;

We can just do:

for (int i = 0; i < 5; i++)
    inner: for (j = 0; j < 3; j++)
        if (i * j == 10) 
            continue inner;

The alternative is cleaner, more intuitive, and simpler.

I saw another post where they suggest doing break 1; to break the first loop, but that doesn't refactor well if the number of loops changes, you have to carefully make sure the numbers are correct.

Neme12 commented 6 years ago

@Korporal It doesn't get more imperative than goto.

DavidArno commented 6 years ago

@Korporal, I think you need to update your account image. Something like the following seems more appropriate:

diplodocus

[Picture copied from here]

Mafii commented 6 years ago

@Korporal actually, read my comment again. It says "sarky" which means sarcastically if my brain doesn't abandon me fully.

Neme12 commented 6 years ago

@Korporal

I wasn't being "snarky" I was actually expressing sarcasm

https://en.wiktionary.org/wiki/snarky

snarky (comparative snarkier, superlative snarkiest)

  1. (informal) Snide and sarcastic;
jnm2 commented 6 years ago

I read it as 'snarky,' too; however, either word is accurate here. Let's not derail the discussion with further sarcasm.

Korporal commented 6 years ago

@DavidArno - That's quite amusing but lets recognize please that some of the counter arguments to the proposal to support break label are actually advocating use of unconstrained goto whereas this proposal leads toward avoiding use of unconstrained goto- this begs the question of the who the real dinosaurs are...

DavidArno commented 6 years ago

@Korporal, the real dinosaurs are those that write code that needs either a break label or a goto to escape nested loops. Sorry if I didn't express it clearly enough before.

Korporal commented 6 years ago

@DavidArno

@Korporal, the real dinosaurs are those that write code that needs either a break label or a goto to escape nested loops. Sorry if I didn't express it clearly enough before.

That's an interesting viewpoint David but I see no empirical evidence that exiting nested loops with a break label mechanism is as poor a coding practice as using an unconstrained goto. It's noteworthy too how nobody has yet responded to my earlier post above which was contesting another unsupported claim that the very idea of nested loops is itself something for the dinosaurs.

CyrusNajmabadi commented 6 years ago

Favoring the introduction of functional mechanisms over something as rudimentary and helpful as breaking out of a labelled loop seems - to me - quite laughable.

That's totally fine to have as an opinion. But, like many opinions, it is not something necessarily held by others.

To me, for example, the features that have been worked on and added have been orders of magnitude more important than making a new way to break out of a nested loop (Esp. when there's already a sufficient way to do it already).

You can disagree, and that's totally fine. But, at the end of the day, if a convincing case cannot be made for why this is "so darn valuable", then the LDM is going to pick features they think are worth it.

That's quite amusing but lets recognize please that some of the counter arguments to the proposal to support break label are actually advocating use of unconstrained goto whereas this proposal leads toward avoiding use of unconstrained goto- this begs the question of the who the real dinosaurs are...

The counterargument is: "there are already facilities in the language to address this concern if/when you run into it. The added feature doesn't substantively improve enough on what is already available".

--

You felt i was berating you before when i made this point. But i'm really not. I'm just trying to help understand that a lot of language decision is dependent on marginal benefit. i.e. If the improvement is exceptionally minor, then it's not going to be taken above features that deliver much larger marginal gains.

Korporal commented 6 years ago

@CyrusNajmabadi

You felt i was berating you before when i made this point. But i'm really not. I'm just trying to help understand that a lot of language decision is dependent on marginal benefit. i.e. If the improvement is exceptionally minor, then it's not going to be taken above features that deliver much larger marginal gains.

But surely it is not simply the magnitude of the gain that's achieved but the gain at some cost. I completely agree that many of the recent changes are superb (in particular for me the extended support for refand the support for the unmanagedgeneric constraint and unsafepointers to generic types).

So although the benefit of break label may be relatively small if it's cost is correspondingly small then surely it's worthy of serious consideration on that basis? This whole concept, used in software all the time is often informally described by the term "low hanging fruit".

If we were to rank suggested language changes on the basis of their benefit/cost ratio then that would be somewhat more objective but I have no idea if such a scheme is used or not by the LDT. Much of my persistence here is because it seems (to me) that the cost of this change is probably rather small in comparison to the benefits:

The cost for all of these? I am not able to objectively estimate that but it should be estimated and must be in order to rank this proposal. I suspect this is a small change to the C# compiler code base.

So yes I agree the benefits provided by this are not world shaking by any means BUT the cost is probably far less than many of the other changes that are being considered by the LDT.

I'd like to add too that when I first began to use C# I recall one day actually writing break search_loop;and was a bit surprised that this wasn't supported.

CyrusNajmabadi commented 6 years ago

So although the benefit of break label may be relatively small

I think one of the problems here is that the benefit for some people (including myself) is almost 'zero'. i mean... i have perfectly viable solutions to the problem today. You don't happen to like the existing solutions, but that doesn't mean that that will make others think that your solution is warranted. :-/

To address your specific points:

Thank goodness :) If it wasn't, it would never be accepted given what it adds to C# :)

Definitely makes things more palatable. As a language designer, i def appreciate that.

Right... and that's somewhat of an issue. I don't see the need to add eye-candy versions of things that have perfectly reasonable solutions already in the language.

I like syntactic-sugar when it really substantively improves things. For example, i could try to replicate pattern matching through different means. But it would likely excessively bloat my code, and make it harder to see teh forest for the trees. So in that case, pattern-matching is very useful 'sugar'. Here, i'm not seeing it. It actually seems effectively equivalent break label; vs goto label;... All that work to get something just slightly different from what i already have today...

I don't really see things that way. I don't really think there's a shortcoming here. You can already deal with this issue just fine (IMO) with the language as is. Java needs this feature because java doesn't have plain gotos. But since we have gotos, we don't really need this.

That's not a virtue to me. I don't see a problem using goto here.

I don't really see that either. At best, to me, it's subjective. I think it's totally clear what a "goto label" means. So, when i see it in a loop, it is completely clear to me what is happening.

--

I hope this helps clarify that your perception of this issue is certainly necessarily one held by others.

CyrusNajmabadi commented 6 years ago

The cost for all of these?

Note: Cost is def part of hte evaluation. However, to me, "cost" comes after the first "should we even do this?" appraisal. As i've pointed out above, i don't actually see this as being somethign that C# does. I think the language handles this sort of situation just fine, with code that is very straightforward to me.

If you want to continue the loop, you jump to the appropriate place in the loop. That totally makes sense to me. If you want to break out of hte loop, you jump to after the loop. That also makes total sense to me. In both cases you're saying exactly where you want to go, and it's clear what will execute next.

I actually find labeled-loops less clear because i can "break/continue" to that label, but then have to understand that even though the label is above the loop, that the break/continue will not take me to the label, but will instead take me somewhere in the loop instead (like before the next increment/condition of a for-loop).

HaloFour commented 6 years ago

@Korporal

I don't have a link to it handy but it's described that every language feature proposal needs to reach a score of 100 in order to be considered for implementation and every language feature starts with a score of -100. That negative initial score represents the costs of even going down the path, from estimating, design meetings, specification, implementation, testing, tool and support in perpetuity. A minor language feature might not have additional associated costs, but it always has at least those previously mentioned costs. Being "easy" is a virtue, but it's not enough of a virtue unto itself. The proposal still has to be compelling, at least to a language designer who would be willing to "champion" it through the process.

jnm2 commented 6 years ago

@CyrusNajmabadi I find labeled loops more clear because it's more consistent with my existing mental model of breaking and continuing. That's what I'm primarily thinking about. goto is not the clearest means to that end. It's an implementation detail that doesn't necessarily tell you what i was thinking.

jnm2 commented 6 years ago

@HaloFour https://blogs.msdn.microsoft.com/ericgu/2004/01/12/minus-100-points/?

CyrusNajmabadi commented 6 years ago

@jnm2 That's fair. But i still don't think it merits the change (personally) :)

It's an implementation detail

They're all implementation details. :D

jnm2 commented 6 years ago

They're all implementation details. :D

No, it's an implementation detail of the decision to break the loop or to continue the loop. What am I going to name the label? break_2? break_outer_loop? That's a sign that goto is the wrong abstraction level. No different than inlining a non-obvious calculation when you should be putting it behind a named helper method.

Korporal commented 6 years ago

@CyrusNajmabadi @HaloFour - Well this has been an interesting discussion and I appreciate people's responses even if we do disagree. But one thing Cyrus said caught my attention (and not for the first time in this entire discussion):

If you want to break out of the loop, you jump to after the loop.

This is actually advocating gotoand it is almost universally accepted that gotois undesirable and can be the cause of many problems. It's also true that many organizations bluntly refuse to allow use of gotofor these kinds of reasons and developers are compelled to address this during code reviews.

So the very "solution" suggested by Cyrus and some others is not even an option for most developers simply because gotois simply disallowed and for very good reasons.

The breakand continueare not the same as gotobecause the breakforces a gototo the end of the loop and the continueforces a gototo the start of the loop. I can never ever read break inner_loop and have any nagging doubts about where execution will go or what would happen if the label were moved later on or code inserted between the label and the loop because these situations cannot possible arise whereas the can with goto.

Given that gotois universally loathed I am surprised that a proposal that eliminates a whole class of use cases for it is being given such harsh treatment.

jnm2 commented 6 years ago

While there are things you can pick apart in what @Korporal is saying, he's also saying something I care about: I don't want there to be a possibility of someone (refactoring tool?) inserting a line before the labels and having the compiler silently accept that. I want a properly abstracted break or continue.

CyrusNajmabadi commented 6 years ago

This is actually advocating gotoand it is almost universally accepted that goto is undesirable

Sorry, i don't believe that personally. There's a reason we included goto in the language, and it's because we do not think it is undesirable. Indeed, cases like this are examples for me of why goto is entirely cromulent.

There are cases where "goto" is very very very bad. And C# was careful to not at all allow those cases. But the cases we allow are ones we feel are fine to use if used wisely (like all features in the language). And that's why you see 'goto' used in Roslyn itself. Since we don't take some sort of ideological stance against it :)

So the very "solution" suggested by Cyrus and some others is not even an option

If organizations are not going to let people code in c#, then it's unclear to me what options i could provide if they may just blacklist those as well.

Say i add that other feature. And an organization then blacklists it because it's "just sugar over a goto". What do we do then? Does a dev from that org come back saying: please add another feature here to do the same thing because my org doesn't allow "continue label"?

CyrusNajmabadi commented 6 years ago

What am I going to name the label? break_2? break_outer_loop?

Whatever name makes sense to you in your domain. What do you name your labels/variables/symbols today? How do you come up with those names? I would follow the same approach for these labels. :)

CyrusNajmabadi commented 6 years ago

I don't want there to be a possibility of someone (refactoring tool?) inserting a line before the labels and having the compiler silently accept that

If you would like certain restrictions on gotos+labels... there are solutions for that. Just write an analyzer. :)

You could just say "in a loop, i can only jump to a label that is at one of these positions". Now you can use the language as you want, but ensure specific restrictions on what is allowed.

You could also create such an analyzer in an hour and have that enforced now, instead of the enormous amount of time and effort necessary to bring into the language. It seems far more ideal to do the former than the latter. :)

HaloFour commented 6 years ago

@Korporal

goto exists for a reason, to solve problems. Like most language features it can be abused. But the goto in C# isn't the goto for which treatises were written against in the 50s and 60s. You can't jump arbitrarily across the entire memory space of the program. You can still write spaghetti, but it's relatively limited in scope.

I look at advice like "never use goto" to be akin to telling a child to never touch the stove. It's certainly good advice and to be heeded, but at some point that child is going to grow up and it's better to learn why they shouldn't touch the stove and how/when to appropriately touch the stove.

If C# were to get something like this feature I'd rather it not copy Java because I completely agree with @CyrusNajmabadi that the ability to use break/continue to a label and not actually have execution flow jump to that label is itself a source of confusion.

CyrusNajmabadi commented 6 years ago

If C# were to get something like this feature I'd rather it not copy Java because I completely agree with @CyrusNajmabadi that the ability to use break/continue to a label and not actually have execution flow jump to that label is itself a source of confusion.

Honestly, if someone wants this, i think the best thing would simply to be to write an analyzer that enforces your desired label placement plus gotos.

So, for example, if you want to goto loop_label inside (say) a for-loop, then the loop label either needs to be right before the end of the 'for' loop (so that incrementors/conditions run), or it needs to be hte statement immediately following hte loop.

If you are in a loop and you goto something that doesn't abide by these rules, your analyzer flags it and says "no j00!" and you fix it. Now you have the language feature with the same "guide rails/restrictions" that are felt to be valuable from java. You can now use htis feature in your org because it isn't 'unrestricted goto', but is instead just the feature that you want, just with the word "goto" instead of "break/continue" and with hte label specified in a different location.

Korporal commented 6 years ago

@CyrusNajmabadi - Once again you are conflating break label and continue label with goto label. The latter places (few) restrictions on where the label can be situated within the code whereas the former places very specific restrictions and thereby eliminates a whole set of potential changes that a developer could make.

Yes I can "emulate" break label or continue label with goto label but we cannot emulate goto label with either break label or continue label and so that reduces the scope for future bugs and complexity.

At this stage - I'm prepared to be proven wrong - it seems to me that the only genuine use case for goto today is for exiting loops and if that is it's sole purpose or use then lets admit it it is not really fit for purpose.

CyrusNajmabadi commented 6 years ago

Once again you are conflating break label and continue label with goto label. The latter places (few) restrictions on where the label can be situated within the code

"Honestly, if someone wants this, i think the best thing would simply to be to write an analyzer that enforces your desired label placement plus gotos."

Korporal commented 6 years ago

Leaving the exiting or continuation of loops aside - what are the use cases for gotoin C# today? Could it be the case that by adding break labeland continue label there really are no use cases any more for goto?

CyrusNajmabadi commented 6 years ago

and so that reduces the scope for future bugs and complexity.

If you are worried about future bugs/complexity, then write an analyzer that checks this for you. IN the entire time that discussion has been going on that analyzer could have been written, tested, packaged to nuget and made available to your org :D

CyrusNajmabadi commented 6 years ago

Leaving the exiting or continuation of loops aside - what are the use cases for gotoin C# today?

Whatever makes sense for your domain and your coding styles.

I'm honestly not sure why it's relevant... even if leaving/continuing loops were the only purpose for gotos... so what? Then use them for that purpose :) If that's their sole purpose then there is definitely less of a need for something else that then serves that exact same purpose.

Now, if you feel like unrestricted gotos are a problem... that's easily solvable. Like with any unrestricted language feature, you can add your own personal restrictions to them through analyzers. That's why we wrote analyzers in the first place. So that people/orgs/API-devs/etc. could add whatever rules they wanted that they felt would constrain the 'unrestricted' full form of C# into one they felt was appropriate for their needs. :)

jnm2 commented 6 years ago

@CyrusNajmabadi

You could also create such an analyzer in an hour and have that enforced now, instead of the enormous amount of time and effort necessary to bring into the language. It seems far more ideal to do the former than the latter. :)

If this was my only concern, of course I'd agree with you here.

If break foo; and continue foo; turn into goto break_foo;...break_foo: and goto continue_foo;...continue_foo:, and require an analyzer to make sure it doesn't get messed up, it's obvious that it's the wrong abstraction level. It's going to require more mental cycles to read the code and reconstruct what's actually happening, and more mental cycles when making changes nearby.

The name of the loop is arbitrarily far away from the declaration of the loop. This is the part that bothers me the most about the semantic gap.

Neme12 commented 6 years ago

What am I going to name the label? break_2? break_outer_loop? That's a sign that goto is the wrong abstraction level.

What are you going to name the loop? loop_2? outer_loop? I don't see how this is any different.

jnm2 commented 6 years ago

@Neme12 But without break_ or continue_ in it. See https://github.com/dotnet/csharplang/issues/1597#issuecomment-397693701.

CyrusNajmabadi commented 6 years ago

and require an analyzer to make sure it doesn't get messed up, it's obvious that it's the wrong abstraction level.

By this logic... the recommendation of any analyzer to restrict what people can do with c# is "the wrong abstraction level".

It's going to require more mental cycles to read the code and reconstruct what's actually happening, and more mental cycles when making changes nearby.

I honestly don't see why that's the case. why would "goto exit_inner_loop" be hard to reconstruct what was happening? I mean i have to see where that label is and then figure out that i'm going to jump to there... but the same is true of "break inner_loop". I still need to find that label, then figure out that i'm going to jump to that then figure out what comes after that (since that's where i'm actually going to jump to).

I don't buy the mental-cycles argument, because i have to expend these mental cycles in both cases. And, frankly, i find the 'straight goto' case less cycles to understand because htere's no 'special' logic happening. I'm just jumping right to where i said i would jump. The loop-label case is more cycles for me because if i do something like "continue loop_label" i have to realize that i'm actually jumping not to where the label is, but to this interesting 'pseudo-point' before the incrementors/condition.

jnm2 commented 6 years ago

By this logic... the recommendation of any analyzer to restrict what people can do with c# is "the wrong abstraction level".

You're picking this single part out of what I said and applying my conclusion as though it was the only problem I mentioned. My argument is that, taken all together, there are enough reasons to show that goto is a poor workaround.

CyrusNajmabadi commented 6 years ago

The name of the loop is arbitrarily far away from the declaration of the loop. This is the part that bothers me the most about the semantic gap.

I guess i don't have an issue with that :) Primarily because the code becomes much simpler from my perspective. There's no confusion about exactly what is happening. I hit the goto and i go right where it says i will go. :)

But i will def say that i can see your perspective on this. It's just not one i personally share :)

Korporal commented 6 years ago

The only reason anyone would even code a gotothat jumps to a prior point in the code is - like it or not - to implement some form of repetition - a loop. Given that C# offers plenty of looping options itself there is no reason I can see for needing a gotothat jumps to a prior point other than the fact that the continuekeyword offers no label support.

This therefore amounts to the LDT saying this to the community:

"We really can't be bothered thinking about the specifics of exiting within nested loops or continuing within nested loops so rather than think about this we just offer the blunt weapon gotothat will - if used cautiously by you - let you do this - but be careful because if someone later moves a label to..."

CyrusNajmabadi commented 6 years ago

You're picking this single part out of what I said

Sorry, i broke your post into the two main points i thought were interesting, and i addressed the separately. If you feel like i haven't covered the overall argument you were making, i apologize!

HaloFour commented 6 years ago

@jnm2

In my opinion you'd likely only be applying one of those operations and only at one of the nested loop levels. In which case you'd have a logical reason for wanting to either break or continue out of that loop and the label should be named for that reason. If you have to both break and continue out of multiple levels of nested loops I'd have to seriously question what the code was doing. Naming this label should not be hard.

That's also partly why I don't think labeling the loop makes sense. Any name you could assign to the loop wouldn't describe why you'd want to interrupt iteration within that loop.

CyrusNajmabadi commented 6 years ago

Given that C# offers plenty of looping options itself there is no reason I can see for needing a gotothat jumps to a prior point other than the fact that the continuekeyword offers no label support.

And yet, here we are :)

I don't get how this helps the argument. We're nto discussing C# 1.0 and what we would want to do then. We're discussing C#8.0+ and any potential changes we might make there. That you feel that any 'goto' use cases could be replaced by 'named break/continue's isn't really relevant. We're not likely to replace a mechanism felt to be perfectly fine by another just because they're effectively isomorphic.

--

Note: if this was C# 1, i would certainly be amenable to an argument about not having 'goto' at all, and addressing these use cases through the 'restricted continue/break label construct'. But we're well past that point...

CyrusNajmabadi commented 6 years ago

I'm really tempted to just write the analyzer at this point... But i have some more pressing work to do :) But honestly, if you want a feature that restricts C# to a subset of what is possible, then analyzers are the ideal option for you :)

jnm2 commented 6 years ago

@CyrusNajmabadi

Sorry, i broke your post into the two main points i thought were interesting, and i addressed the separately. If you feel like i haven't covered the overall argument you were making, i apologize!

I'm participating in this discussion because I have felt strongly over the years of using goto to simulate labeled breaks that it was an inferior solution, and I have coherent reasons for saying so. It's demotivating when you say that my logic would say any analyzer is the wrong abstraction level; you're matching my conclusion (which is based on more factors than this one) with a single starting point which is not sufficient for me to reach that conclusion, and pointing out that it's absurd. Of course it is, but it's a bit draining to have to point out that I didn't actually make that absurd argument.

Korporal commented 6 years ago

@CyrusNajmabadi

I'm really tempted to just write the analyzer at this point.

I'm also really tempted to fork the repo, implement the change and submit a pull request and be done with this rather simple change.

CyrusNajmabadi commented 6 years ago

I'm also really tempted to fork the repo, implement the change and submit a pull request and be done with this rather simple change.

I would def recommend doing that! That's a core value prop of an open source C#/Vb compiler :)

jnm2 commented 6 years ago

@HaloFour

That's also partly why I don't think labeling the loop makes sense. Any name you could assign to the loop wouldn't describe why you'd want to interrupt iteration within that loop.

I'm listening, and this isn't the first time I've tried going down this path myself. What happens when there are three reasons for exiting or continuing the outer loop?

CyrusNajmabadi commented 6 years ago

I'm participating in this discussion because I have felt strongly over the years of using goto to simulate labeled breaks that it was an inferior solution, and I have coherent reasons for saying so.

I def got that! The purpose in my response was to point out that i felt there was a solution that actually addressed your reasons. It felt like you didn't like the solution for reasons unrelated to the solution itself, but because it was through analyzers. I was pointing out then that that argument then seemed to indicate that analyzers themselves weren't really ever viable if used to restrict the available surface area of C#. Did i misinterpret things? If so, again, apologies.

What happens when there are three reasons for exiting or continuing the outer loop?

What would you do with a labeled_loop in that case? Would you have three labels for the loop? Or just one? How would you indicate the reasons for exiting?

jnm2 commented 6 years ago

@CyrusNajmabadi My argument was that there is an impedence mismatch, and the need for an analyzer was the smallest part of that argument. I'm not saying analyzers are bad.

For example, I'm writing an analyzer to generate and maintain With methods. The need for such an analyzer is one of the reasons I'd ask for a withers feature, but by no means the only reason. That reason can't stand in isolation, but it's a real enough reason when taken with the rest.

What would you do with a labeled_loop in that case? Would you have three labels for the loop? Or just one? How would you indicate the reasons for exiting?

I wouldn't. I'd name the loop. The suggestion to indicate the reason for exiting came from @HaloFour and I am responding to him here, asking the same question you just asked.

Korporal commented 6 years ago

@CyrusNajmabadi

What would you do with a labeled_loop in that case? Would you have three labels for the loop? Or just one? How would you indicate the reasons for exiting?

Why is this relevant? We do not currently indicate the "reasons" for doing things in our code other than the code itself perhaps an if (<expression>) for example. The label attached to the loop simply identifies the loop, the "reason" is contained within the logic that leads to the execution of the breakor the continue or perhaps a comment!

CyrusNajmabadi commented 6 years ago

I wouldn't. I'd name the loop.

Ok... so if you'd name the loop... why can you not just label the appropriate jump points if you were to use 'gotos'. I feel like i'm not getting your argument here. :)

HaloFour commented 6 years ago

@jnm2

What happens when there are three reasons for exiting or continuing the outer loop?

Well, for starters, I wouldn't, because that's awful. I don't write Java like that. I don't allow my devs to write Java like that.

But like anything else that can be named I would choose names that fit the logical reason for wanting to do something. isNotApplicable or tryAgain or alreadyFound or whatever. I actually don't know what the naming conventions are for labels or if they differ from locals because I never use them, in either C# or Java.

CyrusNajmabadi commented 6 years ago

@CyrusNajmabadi My argument was that there is an impedence mismatch, and the need for an analyzer was the smallest part of that argument. I'm not saying analyzers are bad.

Understood. Sorry for trivializing your argument hte away i did.