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

Proposal: infinite loop using while { ... } without (true) #2475

Closed
jnm2 opened this issue Apr 29, 2019 · 34 comments
Closed

Proposal: infinite loop using while { ... } without (true) #2475

jnm2 opened this issue Apr 29, 2019 · 34 comments

Comments

@jnm2
Copy link
Contributor

jnm2 commented Apr 29, 2019

The current standard for conditionless loops is while (true) { ... }.
An alternative is for (;;), but various tools reformat it to for (; ; ) and it looks like a hack.

Both of these are awkward enough that we generally try to avoid ever writing them. Sometimes it's not avoidable. Other times, logic is repeated outside the loop or complexity is moved into the condition in order to get one more thing done before the exit condition when one or more statements before the exit condition would be more natural.

Would you consider allowing (true) to be dropped, leaving only while { to start the loop?

Questions

Since there are no parentheses, does it seem best to require a block statement to follow it?
Should an empty statement be allowed?

Pros

  • It's immediately understandable
  • Factoring both away from and back to having a condition is natural
  • It makes more linguistic sense than "while true"
  • It's an existing keyword
  • The signal-to-noise ratio is nicer when typing and reading
  • The cost to spec and implement (🙋‍♂️) is low

Cons

  • Conditionless while is currently not as common as other forms of loops.

  • If conditionless while becomes more common due to the pressure to stay away from it being gone, this could be seen as a bad thing. Maybe folks will reach for it too soon instead of first trying to structure their loop around the constraint of putting the condition first or last.
    (On the other hand, an IDE can easily detect and offer a fix.)

  • Rust has loop { ... } along with while cond { ... }, so there's a chance that while { ... } is not as discoverable.

@CyrusNajmabadi
Copy link
Member

Tagging @jcouv . is this something you miht champion if @jnm2 did all the legwork here?

@CyrusNajmabadi
Copy link
Member

Note: go is another language with a shorthand for an infinite loop as well. Similar to what @jnm2 is proposing here, they just have their loop keyword, followed by the block. i.e. for { }.

Given that we already have while, i think it's reasonable to have while {} as the C# equiv.

Note: i am in a strange state of both liking this proposal, but also thinking it's so minor as to not really be worth it (even if free!). while (true) just does not really seem like something that need "fixing" to me.

@john-h-k
Copy link

I find it confusing, honestly. while what?!!???!. do { } seems clearer even

@jcouv
Copy link
Member

jcouv commented Apr 29, 2019

A few quick thoughts:

  • while (true) ... isn't that bad ;-)
  • even if we fixed the formatting for for (;;) ... I still would prefer while { ... } or while (true) { ... }
  • I'm wondering whether do { } (without while) would be more logical. On the other hand, while { ... } seems more intuitive and easier to transition to/from.

@CyrusNajmabadi
Copy link
Member

'do' without 'while' is going to annoy someone who wants to write:

do {

}


while (x) {

}

:D

@tannergooding
Copy link
Member

I'm wondering whether do { } (without while) would be more logical

I would logically expect this to do { /* stuff */ } once as that is exactly what it states. It in no way indicates that the block should be looped

@HaloFour
Copy link
Contributor

HaloFour commented Apr 29, 2019

Meh, I'm not a big fan. Infinite loops should not be so common that six characters represents a worthwhile saving. In fact, I don't think infinite loops should be more ergonomic. You should have to very explicitly declare your intent.

The proposed syntax, while { }, also suffers from two problems. First, that is already the beginning of legal syntax. You'd have to then scan to the end of the loop to determine whether or not it is a while-do loop instead of an infinite loop. Additionally, because of that same reason, I think it'd be too easy to accidentally make an infinite loop when you wanted a while-do loop.

@CyrusNajmabadi
Copy link
Member

An alternative is for (;;), but various tools reformat it to for (; ; ) and it looks like a hack.

This seems like an IDE bug. I went and looked at the code here and it loos like we try to support this, but then it fails spectacularly. We legit check:

            // Semicolons in an empty for statement.  i.e.   for(;;)
            if (previousParentKind == SyntaxKind.ForStatement
                && this.IsEmptyForStatement((ForStatementSyntax)previousToken.Parent))
            {

So we know it's empty, but then we go and just do random stuff with the spacing. As if anyone would actually want for (; ; ) here. Very bizarre.

@CyrusNajmabadi
Copy link
Member

The proposed syntax, while { }, also suffers from two problems. First, that is already the beginning of legal syntax. You'd have to then scan to the end of the loop to determine whether or not it is a while-do loop instead of an infinite loop. Additionally, because of that same reason, I think it'd be too easy to accidentally make an infinite loop when you wanted a while-do loop.

There's no while-do loop :) It's a do-while loop. There should be no syntactic ambiguity here.

@sharwell
Copy link
Member

This seems like an IDE bug. I went and looked at the code here and it loos like we try to support this, but then it fails spectacularly.

The behavior has changed a few times. During a review of DotNetAnalyzers/StyleCopAnalyzers#633 we determined that the for syntax is rarely used in C# code.

We intentionally chose for (; ;) as the formatting in StyleCop Analyzers (DotNetAnalyzers/StyleCopAnalyzers#1489).

@yaakov-h
Copy link
Member

Personally I don't like while (true) or for(;;) as it makes it difficult for the reader to locate and understand the exit condition, and can often lead to unintentional infinite loops.

That said, for the times where it is necessary, this would be a welcome improvement.

Since there are no parentheses, does it seem best to require a block statement to follow it?

I would say yes. Allowing an empty infinite statement (while;) seems largely pointless, and if you really really do need it, you can always have an empty block (while { }).

@scalablecory
Copy link

I'd use it but I don't feel it's worth it.

@DarthVella
Copy link

Pros

  • It makes more linguistic sense than "while true"

I have to disagree. The meaning of while (true) meaning is immediately apparent as a "repeat forever" condition. while [nothing] looks more like a false-y statement that will never be entered.

We intentionally chose for (; ;) as the formatting in StyleCop Analyzers

Seeing an infinite loop makes me cry, too. 😛

@AartBluestoke
Copy link
Contributor

Pros

  • It's immediately understandable

is it? while{doSomething();} -- did i just forget to write a condition, or is this expected to continue until something exceptions?

  • It makes more linguistic sense than "while true"

I disagree, as while, without a condition is ambiguous, as you may sometimes never enter a while if the condition is false. If this proposal was for the condition on a DO loop to be optional, then i wouldn't have this disagreement.

  • The signal-to-noise ratio is nicer when typing and reading

SNR of being explicit is quite often worth it, for example long variable names are encouraged

  • It's an existing keyword
  • The cost to spec and implement (🙋‍♂️) is low

Cons

  • Conditionless while is currently not as common as other forms of loops.

Infinite loops are a pit of failure, i think we should make them be as infrequent as possible.

  • If conditionless while becomes more common ...

i don't think we would want to encourage this at a language level ...

(On the other hand, an IDE can easily detect and offer a fix.)

  • Rust has loop { ... } along with while cond { ... }, so there's a chance that while { ... } is not as discoverable.

does this go against your "It's immediately understandable" Pro above?

@mikedn
Copy link

mikedn commented Apr 30, 2019

while{doSomething();} -- did i just forget to write a condition, or is this expected to continue until something exceptions?

Exactly. Hurray for those who accidentally forget to type the condition or accidentally remove it (say cut/paste instead of copy/paste) and end up with valid code that represents an infinite loop.

This is one of those features that start at -100 points and immediately drop to -1000 for being unnecessary and dangerous at the same time.

@dsaf
Copy link

dsaf commented Apr 30, 2019

As far as I understand @gafter is hostile towards proposals that only save typing few characters e.g. dotnet/roslyn#8985 .

@spydacarnage
Copy link

@dsaf - Or maybe @gafter is just against proposals that don't add any value while simultaneously creating ambiguity?

@iam3yal
Copy link
Contributor

iam3yal commented Apr 30, 2019

Don't know whether it's worth mentioning but how about while () { ... }? empty parentheses makes more sense to me which is similar to for (;;) { ... }.

Just my 2c. :)

@stuartstein777
Copy link

I would think:

do{
  // do stuff 
}

would just look like scoping, rather than it being a loop. Some languges have (do) blocks that don't imply looping. I know do is a loop in C# but it feels less clear to me than while

@casperOne
Copy link

I agree that this may be too trivial to implement.

If it is implemented, while I think semantically, do makes more sense than while, I'd prefer not to use do in the event C# takes up something like do expressions from ECMAScript (and chooses to use do in that case):

https://github.com/tc39/proposal-do-expressions

We already have something like that with the new switch syntax.

@iam3yal
Copy link
Contributor

iam3yal commented May 1, 2019

@casperOne The do expression looks similar to the switch expression and imo do makes less sense in the context of this proposal but maybe it's just me.

@marksmeltzer
Copy link

+1 for loop {}. I could also go for repeat {}.

-1 for reusing either do or while or for.

@marksmeltzer
Copy link

I currently use for (;;) {} exclusively because I want to be able to find all if my infinite loops.

The main utility of adding an infinite loop should be the ability to have analyzers reason better about the code, and to allow developers to search their code for them.

Since developers implement infinite loops in many different ways, we have a strong anti-pattern. This isn't about saving keystrokes. Rather, since this use case is very specific and potentially dangerous it would pay dividends to have infinite loops as an explicit operator and to strongly discourage using the other loop flavors to implement infinite loops (sounds like a good opportunity for a code analyzer to address!).

@HaloFour
Copy link
Contributor

HaloFour commented May 1, 2019

@marksmeltzer

IMHO, for (;;) { } and while (true) { } are pretty clearly infinite loops (or more likely loops broken from within the body). I don't see why we need additional syntax here, it should already be obvious to the developer and it is certainly obvious to the compiler. Having a separate language feature specifically for writing infinite loops would seem to encourage their use.

@whoisj
Copy link

whoisj commented May 2, 2019

I believe Rust has a keyword for an infinite loop, which is intentionally different from the conditional loops to help ensure the developer meant "loop forever" and didn't just forget to write a proper condition.

  loop {
    // do things here, forever... or until a break is hit.
  }

... um, yup. Here it is in their documentation.

loop - loop unconditionally

@jnm2
Copy link
Contributor Author

jnm2 commented May 2, 2019

To me a downvote means you disagree, but a confused reaction means I failed to explain something properly. Is there something anyone would like me to clarify?

I'm not a fan of leaving issues hanging for a long time and the response is clearly mediocre, so I'm planning to close this issue soon. If the LDT thinks differently than the GitHub community, they can feel free to reopen.

@mikedn
Copy link

mikedn commented May 2, 2019

Yes, and to quote more from their documentation:

The following list contains keywords that are reserved for current or future use by the Rust language. As such, they cannot be used as identifiers (except as raw identifiers as we'll discuss in the "Raw Identifiers" section), including names of functions, variables, parameters, struct fields, modules, crates, constants, macros, static values, attributes, types, traits, or lifetimes.

So now we have this loop keyword which cannot be used as an identifier (compiler authors will no doubt be very happy that they cannot have a variable named loop) simply because infinite loops are so common in typical code that it's worth having a dedicated keyword.

Hmm, something's fishy about that.

In my experience is that infinite loops are rare. In fact, true infinite loops are almost non-existing. In most cases when people use while (true) an exit condition exists but it's somewhere in the middle of the loop. Usually there are ways to avoid that but sometimes it's not worth the trouble and then we end up with while (true). In other words, this is usually a code smell and should not be encouraged by the language.

@mikedn
Copy link

mikedn commented May 2, 2019

To me a downvote means you disagree, but a confused reaction means I failed to explain something properly. Is there something anyone would like me to clarify?

It seems quite obvious to me - you did not explain why is this necessary. I didn't leave a reaction but you can count me in the "confused" group.

@jnm2
Copy link
Contributor Author

jnm2 commented May 2, 2019

Pushing people to put the condition at the beginning or end of the loop results in extra complexity. This is the first reason from my initial post why while (true) doesn't bring me joy:

Both of these are awkward enough that we generally try to avoid ever writing them. Sometimes it's not avoidable. Other times, logic is repeated outside the loop or complexity is moved into the condition in order to get one more thing done before the exit condition when one or more statements before the exit condition would be more natural.

More reasons in the Pros section:

  • It makes more linguistic sense than "while true"
  • The signal-to-noise ratio is nicer when typing and reading

I think I remember others also asking for the loop keyword in GitHub threads, but I figured there's little chance of a new contextual keyword working out.

@mikedn
Copy link

mikedn commented May 2, 2019

Pushing people to put the condition at the beginning or end of the loop results in extra complexity.

But what's complex about it? The language has a construct that allows you to repeat a block of code while a condition is true. If you need to repeat forever then you simply use a condition that is always true - true.

And even if it's complex, how often is such a loop needed?

@jnm2
Copy link
Contributor Author

jnm2 commented May 2, 2019

while (true) isn't complex. Going out of your way to avoid the awkwardness of writing while (true) is what results in complexity: adding flags and setting them at different points in time, torturing the condition to stuff everything in there (pattern matching is making this more appealing), or copying and pasting part of the loop before or after. These all lower maintainability compared to while (true) { or while {.

If while (true) doesn't seem awkward to you, or you think that putting the condition first or last is a good thing no matter the cost, then you disagree with my premises. That's fine; you're in good company in this thread.

And even if it's complex, how often is such a loop needed?

About 1:100 in the Roslyn codebase according to @CyrusNajmabadi. This feels like the right order of magnitude to me; I think the NUnit codebase and my own projects are around 1:100 as well. while (true) does take more mindshare than 1:100 as I'm writing or editing a loop though.

@jeroen-mostert
Copy link

I never avoid writing while (true) { only because I find writing (true) particularly burdensome or unintuitive. I avoid writing it because loops with a single termination condition are easier to reason about. When I've decided an infinite loop with explicit breaks is worth it anyway I'll use it, of course, but one thing that definitely is never an argument for this one way or another is whether I have to write (true).

As for linguistics, I don't see any meaning of "while" without a condition that makes sense: the loop certainly won't run for "a while", it'll run forever (if we don't do anything to prevent it), nor is it "whiling" away, as that implies that nothing is really happening (whereas most such loops in fact have a lot of very interesting things happening). As others have said, it reads more like "while what?" If you want your infinite loop to make complete linguistic sense, while (1 == 1) certainly is an option, but there's a reason while (true) has been established as an idiom -- it's the simplest possible form that's still completely understandable from the base semantics (in this it has a leg up on for (;;), which requires an explicit rule that the empty condition means there's no termination at all, but of course for (;;) has also already established itself as an idiom).

I agree loop would be preferable if there was to be a dedicated keyword for an infinite loop with explicit exits -- but I also agree that the chance of getting loop this late in the game are very slim. I certainly don't think while { } would be an improvement, though, and I directly disagree with the assertion that "it would be immediately understandable" -- but that's just because my natural instincts are to become very careful when I see something that looks a lot like something I already understand, but clearly is not the same as the thing I already understand, as I might be missing some subtlety of semantics that's documented elsewhere. As it so happens there is none with while { }, but I wouldn't have to wonder in the first place with while (true), even if I'd only seen terminating loops up to that point -- it will indeed run until true is no longer true.

@HaloFour
Copy link
Contributor

HaloFour commented May 2, 2019

@jnm2

Going out of your way to avoid the awkwardness of writing while (true) is what results in complexity: adding flags and setting them at different points in time, torturing the condition to stuff everything in there (pattern matching is making this more appealing), or copying and pasting part of the loop before or after. These all lower maintainability compared to while (true) { or while {.

I kind of disagree with this premise. While everything is certainly on a scale, I think that having a bool flag declared ahead of time makes it easier to reason about these kinds of loops. The identifier name should make it clear why the loop would end and you should be able to easily follow its assignment to find when it could change, at least better than trying to find a break or return somewhere within the loop body. It may make things slightly more difficult to write (although I can't say that I've ever found this onerous) but I think it makes it significantly easier to read and maintain.

And if you disagree or find the "contortion" to be "torturous", you already have several ways to write these loops.

@jnm2
Copy link
Contributor Author

jnm2 commented May 2, 2019

These are good reasons and they give me a better sense of the community's mindset. Thanks all, I appreciate it!

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

No branches or pull requests