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

Infinite loop #363

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions proposals/infinite-loop.md
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
vmishenev marked this conversation as resolved.
Show resolved Hide resolved
// 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.