Language: List element repetition.

Created on 15 Aug 2020  路  2Comments  路  Source: dart-lang/language

Dart list literals have spreads and iteration, which makes some things possible in an iterable which would otherwise require a specialized constructor call. For example List.filled(42, "") can be written as [for (var i = 0; i < 42; i++) ""].
That's still not very concise, though.

With non-nullable types, initializing a list to a all the same value becomes more common, so what if we had a more concise way t o do the above: ["" x 42].
This is a special element-entry syntax which only works in list literals (because it makes no sense to repeat elements in a set or map).

It treats a single x (that's a lower-case X letter) as an infix operator, where the second operand must be an integer, and the first operand must be an expression (or, potentially, a list element).

PROs:

  • It would allow brief list initialization like ["" x 42] or [0, 1 x count, 0].
  • It could generalize to more complex operations like [(...otherList) x 2]. (This could either evaluate the spread operation twice, or just copy the elements introduced by the first spread one more time).

CONs:

  • Doesn't always read well. If x is an integer variable, then [x x x] is valid. More likely would be [x x length] or [value x x], which are still tricky to read because the same letter means different things.
  • Not as general as [for (var x in 0..42) ""], which is another way to reduce verbosity of loops. Or maybe even [for (0..42) ""] (where it would be a default variable name if you needed it). That is, if we had other shorthands, then x wouldn't be as needed, so it might be too specialized.
feature

Most helpful comment

That's clever, but probably not as efficient as I'd want it. The reason for making it a language feature would be that it can be efficiently optimized, used inside list literals, and it'd be more convenient than List.generate(42, 2).

For [2] * 42 by itself, it's actually decently usable.
Inside another list, it will require a spread, so [1, ... [2] * 42,. 3] instead of [1, 2 x 42, 3]. Still close.

In practice I'd probably make it a method on Iterable, so it could also be used in set literals:

extension X on Iterable<T> {
  Iterable<T> operator*(int repeat) {
    RangeError.checkValueInRange, repeat, 0, null, "repeat");
    return () sync* {
      for (var i = 0; i < repeat; i++) yield* this;
    }();
  }
}

Then you would always need a spread: [...[2] * 42], {...[2] * 42}.

All 2 comments

extension<T> on List<T> {
  List<T> operator*(int rhs) {
    List<T> rv = [];
    for (int i = 0; i < rhs; i++) {
      rv.addAll(this);
    }
    return rv;
  }
}

void main() {
  final list = [2] * 42;
  print(list);
}

Prints [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]

That's clever, but probably not as efficient as I'd want it. The reason for making it a language feature would be that it can be efficiently optimized, used inside list literals, and it'd be more convenient than List.generate(42, 2).

For [2] * 42 by itself, it's actually decently usable.
Inside another list, it will require a spread, so [1, ... [2] * 42,. 3] instead of [1, 2 x 42, 3]. Still close.

In practice I'd probably make it a method on Iterable, so it could also be used in set literals:

extension X on Iterable<T> {
  Iterable<T> operator*(int repeat) {
    RangeError.checkValueInRange, repeat, 0, null, "repeat");
    return () sync* {
      for (var i = 0; i < repeat; i++) yield* this;
    }();
  }
}

Then you would always need a spread: [...[2] * 42], {...[2] * 42}.

Was this page helpful?
0 / 5 - 0 ratings