フラミナル

考え方や調べたことを書き殴ります。IT技術系記事多め

【Flutter/Dart】イテレーターで副作用のある処理を呼んではいけない

Flutter/Dart学習中です。 Iterator の一つである where を見ていたところこの記載があったので咀嚼してみます。

Creates a new lazy Iterable with all elements that satisfy the predicate test.

The matching elements have the same order in the returned iterable as they have in iterator.

This method returns a view of the mapped elements. As long as the returned Iterable is not iterated over, the supplied function test will not be invoked. Iterating will not cache results, and thus iterating multiple times over the returned Iterable may invoke the supplied function test multiple times on the same element.

述語テストを満たすすべての要素を持つ新しい遅延Iterableを作成します。

マッチする要素は、返されるイテレート可能の中では、イテレータの中と同じ順序になります。

このメソッドは、マップされた要素のビューを返します。返された Iterable が反復処理されない限り、与えられた関数テストは起動されません。イテレートしても結果はキャッシュされないので、返された Iterable を複数回イテレートすると、同じ要素に対して複数回関数テストが実行される可能性があります。

where method - Iterable class - dart:core library - Dart API

void main() {
  
  int counter = 0;

  final numbers = <int>[1, 2, 3, 5, 6, 7];
  var result = (){
    return numbers.where((n) {
      counter++;
      return n < 5;
    });
  }(); // (1, 2, 3)

  print("--before run--");
  print("counter: $counter");
  print("");
  print("--first time--");
  print("result: $result");
  print("counter: $counter");
  print("");
  print("--second time--");
  print("result: $result");
  print("counter: $counter");
}

<int>[1, 2, 3, 5, 6, 7]; から 5未満の値をとりだす処理をかきました。

これを実行するとこうなります。

--before run--
counter: 0

--first time--
result: (1, 2, 3)
counter: 6

--second time--
result: (1, 2, 3)
counter: 12

counter の値が2回目は倍になっていますね。これは result を2回呼び出しているためです。

ドキュメントによるとイテレーターは遅延実行されるため、result を呼び出したタイミングで処理が行われるので呼び出す都度評価されます。

一方で result をこのような形にかえると一度しか実行されませんし、result を呼び出す前にすでに処理が確定しています。

void main() {
  
  int counter = 0;

  final numbers = <int>[1, 2, 3, 5, 6, 7];
  List<int> result = (){
    List<int> ret = [];
    for (final n in numbers) {
      counter++;
      if (n < 5) ret.add(n);
    }
    return ret;
  }(); // (1, 2, 3)

  print("--before run--");
  print("counter: $counter");
  print("");
  print("--first time--");
  print("result: $result");
  print("counter: $counter");
  print("");
  print("--second time--");
  print("result: $result");
  print("counter: $counter");
}
--before run--
counter: 6

--first time--
result: [1, 2, 3]
counter: 6

--second time--
result: [1, 2, 3]
counter: 6

もしくは toList を呼び出し Iterator から List に変えることで遅延処理を toList で確定させることもできます。

void main() {
  
  int counter = 0;

  final numbers = <int>[1, 2, 3, 5, 6, 7];
  var result = (){
    return numbers.where((n) {
      counter++;
      return n < 5;
    }).toList();
  }(); // (1, 2, 3)

  print("--before run--");
  print("counter: $counter");
  print("");
  print("--first time--");
  print("result: $result");
  print("counter: $counter");
  print("");
  print("--second time--");
  print("result: $result");
  print("counter: $counter");
}