Language: Improved loop constructs and scoping.

Created on 10 Jan 2019  路  6Comments  路  Source: dart-lang/language

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:

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:

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:

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.

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:

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)

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:

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:

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.

(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)

feature

Most helpful comment

In Dart we should be able to loop over maps.

for(var key, var value in someMap) {
  // Stuff
}

Loop over list with index

for(var index, var value in someList) {
  // Stuff
}

As in https://github.com/dart-lang/language/issues/68#issuecomment-435259743, if the dart gets support for language level tuples looping over maps will produce tuple of (key, value), which can be deconstructed into two variables in process. This is also applicable for looping over list with index.

for(var index, value in someList) {
  // Stuff
}

All 6 comments

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.

O_o. I didn't even know Dart worked that way. I assumed the scope would cover the condition.

This breaks with the rule that dart scopes and blocks are the same thing.

Is that a rule anyone knows? I think we will likely end up relaxing this if we do some kind of lightweight pattern-matching if statement:

Object o = ...
if (int i = o) {
  print(i.isEven);
}

So I think it's reasonable to extend the scope for do-while too.

One thing I have needed is counting iterations and detecting the first and last iteration.
Can we make that a language feature?
Counting and first iteration could be done by injecting a variable.
Last iteration of for-in could be done by calling moveNext immediately after capturing 'current'.

Something like this...

for (var e in items) {
  if (!for.first) write(', ');
  if (for.last) write('and ');
  write(e);
}
void printList(header, footer, items) {
  for (var item in items) {
    if (for.first) print(header);
    print('${for.count}. $item');  // for.count is 1-based, for.index is 0-based.
    if (for.last) print(footer);
  }
}

We could use a loop label to access iteration properties but I'd hate to have to go back and name the loop unless we are in a situation where we would name it anyway for break/continue (e.g. if (OuterLoop.first) ...). I don't like statement labels on loops because they bury the main keyword of the labelled construct.

A very simple improvement would be a simple keyword for while (true).
Middle-exits feel less forced when I don't have to write a dummy condition.
Perhaps do-while minus the while.

The issue I have with labels hiding the for also applies to if (test) hiding the break. We could make the condition a subordinate to the action:

do {
  doSomethingAlways();
  break if test;
  doSomethingBetween();
}

Now the keyword-colored do and break stand out and there are fewer extra tokens.
I think this looks cleaner than do-stuff-while-stuff and is easy to edit to add another condition.

Something like this...

for (var e in items) {
  if (!for.first) write(', ');
  if (for.last) write('and ');
  write(e);
}

What about

for (var e in items; int index) {
  if (index == 0) write(', ');
  if (index == items.length) write('and ');
  if (index % 2 == 0) {
    write('even');
   } else {
     write('odd');
  write(e);
}

In Dart we should be able to loop over maps.

for(var key, var value in someMap) {
  // Stuff
}

Loop over list with index

for(var index, var value in someList) {
  // Stuff
}

As in https://github.com/dart-lang/language/issues/68#issuecomment-435259743, if the dart gets support for language level tuples looping over maps will produce tuple of (key, value), which can be deconstructed into two variables in process. This is also applicable for looping over list with index.

for(var index, value in someList) {
  // Stuff
}

@l7ssha's proposal would be especially useful in combination with collection-for. With that, you could (for example) write this mapMap() call like this:

var closure = transitiveClosure({
  for (var name, package in packages)
    name: package.dependencies.keys
});

Right now, there's not an elegant way of doing that with collection-for.

@nex3 you can write:

  var closure = transitiveClosure({
    for (var e in packages.entries)
      e.key: e.value.dependencies.keys
  });

but I agree that naming key and value is better for understanding.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

panthe picture panthe  路  4Comments

leonsenft picture leonsenft  路  4Comments

listepo picture listepo  路  3Comments

kevmoo picture kevmoo  路  3Comments

wytesk133 picture wytesk133  路  4Comments