dart-lang / dart-lang/language

Improved loop constructs and scoping.

Open
#171 6 comments 25 reactions 1 assignee Claimed by @lrhn View on GitHub
feature
Dominant language
TeX
Stars
2.9k
Forks
239
Avg merge
2d 18h
Merged PRs (30d)
14

Description

The Dart loop constructs, `for`/`while`/`do`-`while` are fairly simplistic and low-level. There are a number of improvements that could make them easier to use to write understandable code in less convoluted manners.

# Else-branches
A fairly often occurring issue is to look for something in a list, and then do something if nothing is found.
Example:
```dart
var particularElement;
for (var e in someList) {
if (predicate(e)) {
particularElement = e;
break;
}
}
if (particularElement == null) return;
useElement(particularElement)
```
Here we need an extra check (sometimes an extra `found` boolean if we can't use `null` to represent a missing match).

I sometimes write that with a break instead:
```dart
var particularElement;
found: {
for (var e in someList) {
if (predicate(e)) {
particularElement = e;
break found;
}
}
return;
}
useElement(particularElement);
```
If a loop construct allowed an `else` branch, then it could be written as:
```dart
var particularElement;
for (var e in someList) {
if (predicate(e)) {
particularElement = e;
break;
}
} else {
return;
}
```
The `else` branch is executed *only* when the loop condition becomes non-true. It is skipped over when breaking out of the loop.
(Example in dart:convert: https://github.com/dart-lang/sdk/blob/master/sdk/lib/convert/json.dart#L343).

The labeled-break based rewrite also suggests a possible desugaring.

# Extended Scope of `do`
The `do` loop is sometimes exactly what you need when you want to do some computation before doing the first test. However, any variable used in the test must be declared outside the loop.
```dart
var variable;
do {
something();
variable = someComputation();
other();
} while (variable.isFine);
```
That's annoying when the variable is not needed after the loop.

Instead we should extend the variable scope of the `do` body to also cover the condition, so the above can be written as:
```dart
do {
something();
var variable = someComputation();
other();
} while (variable.isFine);
```
This breaks with the rule that dart scopes and blocks are the same thing. It means that the condition is evaluated in the scope of the block (and it's always a block, we add a block in the specification if the body is a non-block statement to avoid degenerate cases like `do var x = foo(); while (x)`), even if it is lexically outside. It makes sense if we see the entire `do`-loop as a single construct.

(Example in dart:io: https://github.com/dart-lang/sdk/blob/master/sdk/lib/io/stdio.dart#L66)

It also breaks the rule that a `continue` is like a break of the loop body block. or at least, only variables declared prior to any `continue`s can be visible in the condition if a `continue` jumps right to the condition.

# Combined do-while Loops
Another common issue is the fencepost problem. You want to do something in a loop, and then do something *between* rounds. This is often written as:
```dart
while (true) {
doSomethingAlways();
if (!test) break;
doSomethingBeween();
}
```
If we extend the `do`/`while` loop to allow two blocks, combining the `do` and the `while` loop, we can write this much more readably as:
```dart
do {
doSomethingAlways();
} while (test) {
doSomethingBetween();
}
```
This simply generalizes `do`-`while` and `while` loops into one that has an optional `do` part and a potentially empty `while` part.

Here we should again let the scope of the first block extend through the condition and into the second part, because it again makes the construct easier to use. It also works well with the obvious desugaring.

The scope of the `do` part may also be able to extend to an `else` part since it's definitely executed (although I'm not sure where a `continue` in the first, "do", part would go, because going to the condition isn't safe then).

(A continue in the first part will skip to the test. A continue in the second part will go back to the start of the loop. In both cases they work exactly like they are breaking the current block).

(Example from dart:collection: https://github.com/dart-lang/sdk/blob/master/sdk/lib/_internal/js_runtime/lib/collection_patch.dart#L656)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.