-
Notifications
You must be signed in to change notification settings - Fork 370
Infinite loop #363
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
Open
vmishenev
wants to merge
6
commits into
Kotlin:master
Choose a base branch
from
vmishenev:support-infinite-loop
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Infinite loop #363
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
a7e5e1a
Add proposal for infinite loop
vmishenev 5128bd1
Rewrite proposal for infinite loop
vmishenev ca50303
Enhance design section
vmishenev 3209734
Polish section about existing loops
vmishenev 414b92c
Replace readLine with readlnOrNull
vmishenev 8f606b7
Add a link to discussion
vmishenev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
# Infinite loop | ||
|
||
* **Type**: Design proposal | ||
* **Author**: Vadim Mishenev | ||
* **Contributors**: Roman Elizarov | ||
* **Status**: In Progress | ||
* **Prototype**: In Progress (inferring `Nothing` is implemented) | ||
* **Issue**: [KT-27970](https://youtrack.jetbrains.com/issue/KT-27970/Support-an-infinite-for-loop) | ||
|
||
## Summary | ||
|
||
This KEEP introduces a new expression `for { ... }` to indicate an [infinite loop](https://en.wikipedia.org/wiki/Infinite_loop) (also known as a “while true”). | ||
|
||
## Motivation | ||
|
||
Kotlin leads to be laconic programming language, and an infinite loop `while(true) { ... }` (rare `do { ... } while(true)`) might be expressed more concisely. | ||
Besides, a dedicated expression makes an infinite loop immediately understandable. | ||
This usually results in more readable code. | ||
|
||
### Use-cases | ||
|
||
Infinite loops is an idiom that is widely used in Kotlin programming. | ||
In Kotlin the percentage of `while(true)` among all written `while` loops is 19% (in 1.2M repositories from the BigCode tool). | ||
|
||
- Infinite loops are widely used to monitor user input or device activity. | ||
The idiomatic approach to reading all the lines from the input stream until it is over (until `readLine` function returns null): | ||
|
||
```kotlin | ||
while(true) { | ||
val line = input.readLine() ?: break | ||
// process line | ||
} | ||
|
||
``` | ||
Or the while loop can be used for the main game frame which continues to get executed until the user or the game selects some other event. | ||
|
||
- Infinite loops also appear quite often in concurrent programming with coroutines, because various concurrent background processes are often conveniently represented as an infinite loop doing something until cancelled. | ||
|
||
- It is often used along with exit conditions / jump expressions at the middle of a body loop. | ||
|
||
with `when` condition: | ||
|
||
```kotlin | ||
while(true) { | ||
when(await().resultCode) { | ||
RESULT_OK -> break | ||
RESULT_CANCELED -> { | ||
finish() | ||
return false | ||
} | ||
else -> continue | ||
} | ||
} | ||
``` | ||
or inside `try-catch` block: | ||
|
||
```kotlin | ||
while(true) { | ||
try { | ||
// repeated process | ||
} catch(e: InterruptedException) { | ||
break; | ||
} | ||
} | ||
``` | ||
|
||
|
||
The list of use cases is not exhaustive. | ||
In general, an infinite loop is a common form, and usual loops (`while`, `do-while`) with a condition can be rewritten with it and vice versa. | ||
|
||
|
||
### Other languages | ||
|
||
- Go (Golang) has `for { ... }` - loop. Probably `for` stems from `repeat forever`. | ||
|
||
- In Rust there is `loop { ... }`. | ||
|
||
- In C# there was a [proposal](https://github.com/dotnet/csharplang/issues/2475), but the discussion was shut down. | ||
|
||
**Summary** | ||
|
||
The community does not want to encourage the use of infinite loops. In their opinion, it is better to have loops with a condition or flag that can explain a reason of termination. | ||
|
||
`while{ .. }` looks like a user accidentally forgot to type the condition or accidentally removes it (say cut/paste instead of copy/paste) and ends up with valid code that represents an infinite loop. | ||
|
||
and so on (Ada, Ruby, Fortran). | ||
|
||
Remarkably, Rust, Go, Ruby do not have `do-while` loop. So infinite loop can be a way to write it in these languages. | ||
|
||
|
||
## Design | ||
|
||
The proposal is to support infinite loop via the concise `for { ... }` syntactic construction without parameters or conditions. The curly braces `{ ... }` are required and can not be omitted. | ||
It should be used as expression with the result type `Nothing`, but if a loop has a `break` expression, the result type of expression should be `Unit`. | ||
|
||
|
||
## Statement or expression | ||
|
||
### Other languages | ||
|
||
In Golang infinite loop `for { ... }` is a statement. (see [The Go Language Specification: for clause](https://go.dev/ref/spec#ForClause)). | ||
|
||
On the other hand, in Rust `loop { ... }` is an expression. (see [The Rust Reference: infinite loops](https://doc.rust-lang.org/reference/expressions/loop-expr.html#infinite-loops)) | ||
A loop expression without an associated break expression has type `!` ([Never type](https://doc.rust-lang.org/reference/types/never.html)). | ||
Meanwhile, `break` expressions of Rust can have a [loop value](https://doc.rust-lang.org/reference/expressions/loop-expr.html#break-and-loop-values) only for infinite loops, e.g. | ||
|
||
```rust | ||
let result = loop { | ||
if b > 10 { | ||
break b; | ||
} | ||
let c = a + b; | ||
a = b; | ||
b = c; | ||
}; | ||
``` | ||
|
||
### Type inference in Kotlin | ||
|
||
Currently, in Kotlin infinite loops cannot be properly used inside scope functions. The following code does not compile due to type mismatch, since `while` is not an expression and the resulting type of run coerces to Unit (see [KT-25023](https://youtrack.jetbrains.com/issue/KT-25023/Infinite-loops-in-lambdas-containing-returnlabel-dont-coerce-to-any-type)): | ||
|
||
```kotlin | ||
fun foo(): Nothing = // Should be compiled, but Error: Type mismatch: inferred type is Unit but Nothing was expected | ||
run { | ||
while (true) { | ||
doSomething() | ||
} | ||
} | ||
``` | ||
Infinite loop shall be an expression of `Nothing` type (similar to `throw` to mark code locations that can never be reached) so this code compiles. | ||
Meanwhile, the type should be `Unit` if a loop contains `break`. | ||
Despite `return` breaking a loop, it does not make code after an infinite loop reachable so the expression type can be `Nothing`. It allows to compile the following code: | ||
|
||
```kotlin | ||
val x = run<Int> { | ||
while (true) { | ||
return@run 1 // Error: Type mismatch: inferred type is Unit but Int was expected | ||
} | ||
} | ||
``` | ||
Moreover, the proposed infinite loop `for { ... }` with `Nothing`/`Unit` types is backwards compatible with old code. | ||
|
||
### Should existing loops in Kotlin be expresions? | ||
|
||
IDE can have an intention to transform loops with `true` condition into proposed infinite loops. It could make sense to make existing loops with a condition (`do-while`, `while`) expressions. | ||
The existing loops would be used as an expression that allows, for example: | ||
```kotlin | ||
var v = getSmth() ?: while( condition ) { } // Error: While is not an expression, and only expressions are allowed here | ||
``` | ||
|
||
But it breaks backward compatibility with already written code, for example: | ||
|
||
```kotlin | ||
fun foo() = run { | ||
while (true) { | ||
doSomething() | ||
} | ||
} | ||
``` | ||
After this change, `foo` would have `Nothing` type instead of `Unit`. Also, it would cause the compiler error `'Nothing' return type needs to be specified explicitly`. | ||
So the loops with a condition should be left statements. | ||
|
||
|
||
|
||
### Functions with expression body | ||
|
||
`return` is frequently used to exit from an infinite loop as well, but `return`s are not allowed for functions with expression body in Kotlin, e.g. | ||
|
||
```kotlin | ||
fun test() = while (true) { | ||
return 42 // Error: Returns are not allowed for functions with expression body. Use block body in '{...}' | ||
} | ||
``` | ||
This problem can be solved by `break` with a loop value like in Rust. But it deserves another proposal. | ||
|
||
## Feature versus stdlib function | ||
|
||
Infinite loops can be implemented via a function (like `repeat` in StdLib), but it will not support `break`/`continue`. So this feature is the shortest way to support it. | ||
[KT-19748](https://youtrack.jetbrains.com/issue/KT-19748/Provide-some-sort-of-break-continue-like-mechanism-for-loop-like-functions) solves this problem for a such function and loop-like functions (`forEach`, `filter`, etc). But there are some disadvantages of this feature: | ||
|
||
- It seems to be very specified only for stdlib's functions. | ||
- Existed local `return` can become misleading with a labelled `break`/`continue` expression. A user can expect a local `return` should exit a loop-like function at all. It might need to change the behavior of local `return` for loop-like function. | ||
|
||
|
||
```kotlin | ||
(0..9).forEach { | ||
if (it == 0) return@forEach // currently, it has the same behavior as `continue@forEach` | ||
println(it) | ||
} | ||
``` | ||
|
||
|
||
- For a function of infinite loop inside a loop (`for`, `while`), it might require extra labels that make code verbose. | ||
See [KEEP-326](https://github.com/Kotlin/KEEP/issues/326): | ||
an unlabeled `break`/`continue` goes to the innermost enclosing block that is clearly marked with `for`, `do`, or `while` hard keywords: | ||
|
||
```kotlin | ||
for (elem in 1..10) { | ||
(1..10).someInfiniteLoopFunction { | ||
println(it) | ||
if (it == 5) break@someInfiniteLoopFunction | ||
} | ||
} | ||
``` | ||
|
||
- The implementation of the feature is more complicated than infinite loops. | ||
|
||
|
||
## Which keyword is used for infinite loops? | ||
|
||
Main candidates of the keyword: | ||
|
||
- `while { ... } ` | ||
- `for { ... }` | ||
The Golang (Go) uses it. Probably `for` is shortened to `forever`. | ||
- `do { ... }` But `do {} while(...) {}` can be ambiguous so for a user. A user should check the end of a loop to differ `do-while` and `do` loops. | ||
- `loop { ... }` | ||
This keyword is used in Rust for infinite loops. | ||
- `repeat { ... }` | ||
|
||
|
||
The plus of using words `while`, `do`, `for` is that they are existing keywords. Users are already familiar with their semantics. | ||
Otherwise, introducing a new keyword increases syntax complexity of the language. | ||
Also, it can break backward compatibility since old code can have a user function with the same name as the keyword. | ||
|
||
`while { ... } ` can look like that a user forgot to write a condition. At the same time, unlike othe languages, Kotlin does not have a free-form condition for `for` loop. | ||
|
||
|
||
The existing keywords `for` and `while` require `{ ... }` for an infinite loop body. | ||
Contrariwise, for example, the following code becomes ambiguous: | ||
|
||
```kotlin | ||
for (x in xs); | ||
``` | ||
It can be treated either as a for loop with a loop variable `x` and an empty body or as an infinite loop which evaluates expression `(x in xs)`. | ||
|
||
|
||
To sum up, `for` is the best choice for an infinite loop. It does not require intruduction a new keyword and do not have the problem with forgotten condition like `while`. | ||
It seems to read nicely for the case of an infinite loop, changing the motivating example from `Use-cases` section to: | ||
|
||
```kotlin | ||
for { | ||
val line = input.readLine() ?: break | ||
// process line | ||
} | ||
|
||
``` | ||
Additionally, factoring this form both away from and back to having a condition is natural. | ||
|
||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.