JavaScriptのイテレータが持つメソッドをそろそろ知っておきたい人が読む記事

イテレータは今となっては多くのプログラミング言語に存在する概念で、繰り返し処理やループ、ストリームといった対象を抽象化してくれるものです。JavaScriptにはES2015でイテレータが追加されており、JavaScriptを触っている方にとっては既に馴染み深いものとなっています。

とはいえ、JavaScriptのイテレータにはひとつ問題点がありました。それは「**イテレータを直接変換・操作できるメソッドが存在しない**」という点です。従来イテレータが持つメソッドはイテレータから一つ値を取り出す`next`メソッドのみであり[^generator_iterator]、それ以上の機能は何も提供されていませんでした。これにより、Rustなどのイテレータが強い言語に比べてJavaScriptのイテレータは有用性が大幅に低いものとなっていました。

[^generator_iterator]: ジェネレータ関数により作られたイテレータはさらに`return`メソッドと`throw`メソッドも持ちます。

この記事では、この問題を多少解消するプロポーザル「[Iterator Helpers](https://github.com/tc39/proposal-iterator-helpers)」を紹介します。これは2019年7月のTC39ミーティングでStage 2となったプロポーザルであり、ということは近い将来この記事で紹介するメソッドたちが利用可能になることが期待できるということです。

これはあくまでプロポーザルであり、この記事で紹介する内容はお手元のブラウザやnode.jsなどで今すぐ使えるものではないという点はご注意ください。プロポーザルのリポジトリにはPolyfillが用意されていますので、どうしても使いたい場合はそのPolyfillを入れてみましょう……と言いたいところですが、実は今存在するPolyfillはまともに動かないようです。残念ですね。

## イテレータの復習

もしかしたらイテレータのことをあまり知らない方もいるかもしれませんので、まず少しイテレータの解説を挟みます。イテレータは知ってるよという方は次まで飛ばしましょう。

イテレータは値をひとつずつ取り出すことができるデータです。JavaScriptではイテレータは`next`メソッドを持ち、このメソッドを呼ぶたびにイテレータから値がひとつずつ得られます。

イテレータの作り方は主に2種類あり、**イテレータを作る既存メソッドを使う**か**ジェネレータ関数を使う**かの2種類です。ジェネレータ関数というのは`function*`で宣言される関数であり、`yield`式が使えるのが特徴です。以下の例は、既存のメソッドである`Array.prototype.entries`を用いてイテレータを得る例です。

```js:イテレータの例
const arr = [0, 1, 1, 2, 3, 5];
// 配列から[index, value] というペアのイテレータを得る
const entries = arr.entries();

// entriesから値をひとつ得る
console.log(entries.next()); // { done: false, value: [0, 0] }
// 次の値を得る
console.log(entries.next()); // { done: false, value: [1, 1] }
// どんどん値を得る
console.log(entries.next()); // { done: false, value: [2, 1] }
console.log(entries.next()); // { done: false, value: [3, 2] }
console.log(entries.next()); // { done: false, value: [4, 3] }
console.log(entries.next()); // { done: false, value: [5, 5] }
console.log(entries.next()); // { done: true, value: undefined }
```

このように、イテレータの`next`メソッドを呼ぶたびにイテレータから次の値を取り出すことができます。配列の`entries`メソッドによって作られるイテレータは、配列の前から順に`[インデックス, 値]`というペアを順番に発生されます。このように「順番にデータが発生する」という状況を表すのにイテレータは向いています。

`next`の返り値は`done`と`value`プロパティを持つオブジェクトであり、`done`によってイテレータのデータを全部取り出し終わったかどうかを判断できます。

なお、`for-of`文もイテレータに対応しており、これを使ってイテレータを扱うのが便利です。`for-of`文は、イテレータの中身を全部取り出すまで順番にループしてくれます。

```js:for-of文の例
const arr = [0, 1, 1, 2, 3, 5];
for (const [index, value] of arr.entries()) {
  console.log(index, value);
}
// 0 0
// 1 1
// 2 1
// 3 2
// 4 3
// 5 5
// と表示される
```

また、イテレータは`Array.from`などを用いて配列に変換するという使い道もあります。これも`for-of`文と同様に、イテレータの中身を全部取り出して配列に詰める挙動になります。

このようにイテレータは、値が順番に取り出せるという点では配列と似ています。しかし、イテレータには配列には真似できない点がひとつあります。それは**無限イテレータを作れる**という点です。無限イテレータというのは終わりがないイテレータのことで、求められれば何個でも値を発生させ続けることができます。このようなイテレータは配列のように値を最初から全部持っているのではなく、求められたらその都度値を計算するという挙動をします。これはある意味で計算を遅延しているということであり、この遅延こそがイテレータの本質であるとも言えます。

ただ、無限イテレータは扱うのが大変です。上と同じように`for-of`文を使って回すと、イテレータの終わりがないため無限ループとなってしまいます。何らかの方法で自分で処理を打ち切る必要があります。

無限ループの例をややベタですがひとつ紹介します。先ほどから例に使っている`arr`はいわゆるフィボナッチ数列の最初のいくつかを切り取ったものですね。フィボナッチ数列は無限に続くことが知られているので、フィボナッチ数列を表す無限イテレータをジェネレータ関数を使って作ってみましょう。

```js:フィボナッチ数列を出力し続けるジェネレータ関数
function* fib() {
  yield 0;
  yield 1;
  let a = 0, b = 1;
  while (true) {
    [a, b] = [b, a + b];
    yield b;
  }
}

const iter = fib();
```

このように作った`iter`は、`0 1 1 2 3 5 8 13 21 ...`のように無限に値を発生させ続けます。使う側はこんな感じで途中でループを打ち切ってあげましょう。

```js
for (const value of iter) {
  if (value >= 100) break;
  console.log(value);
}
```

イテレータのメソッドが無い状態では、イテレータで出来ることはこれくらいでした。

## イテレータのメソッドたち
では、今回紹介するプロポーザル「Iterator Helpers」でどのようなメソッドが追加されるのかを紹介していきます。名前を見ただけで分かる方もいるでしょうから、まず名前を列挙しておきます。

- `map`
- `filter`
- `take`
- `drop`
- `asIndexedPairs`
- `reduce`
- `toArray`
- `forEach`
- `some`
- `every`
- `find`

### `map`

`map`メソッドは、イテレータの各値が与えられた関数で変換された新しいイテレータを返します。つまるところ、配列の`map`メソッドのイテレータ版です。

```js:mapメソッドの例
const arr = [0, 1, 1, 2, 3, 5];
const iter1 = arr.values(); // arrの要素を順番に発生させるイテレータ
const iter2 = iter1.map(x => x * 2); // iter1の各要素が2倍されたイテレータ
for (const val of iter2) {
  console.log(val);
}
// 0 2 2 4 6 10 が表示される
```

また、これはイテレータ一般に言えることですが、イテレータの各要素の変換は実際にその要素が必要になるまで遅延されます。つまり、要素が`next`で取り出されるときにはじめてその要素に対する変換が発生するということです。`map`に渡すメソッドに`console.log`を仕込んでみるとこのことが分かります。

```js:mapメソッドの例
const arr = [0, 1, 1, 2, 3, 5];
const iter1 = arr.values();
const iter2 = iter1.map(x => {
  console.log(x, "を変換します");
  return x * 2;
});
for (const val of iter2) {
  console.log(val);
}
```

これを実行すると次のように表示されるでしょう。

```
0 を変換します
0
1 を変換します
2
1 を変換します
2
2 を変換します
4
3 を変換します
6
5 を変換します
10
```

すなわち、`for-of`文によって`iter2`から値が取り出される瞬間に、内部では「`iter1`から値を取り出す→`map`に渡された関数を呼び出して値を変換する」という処理が起こっていることになります。

### `filter`

`filter`も配列の`filter`メソッドのイテレータ版です。

すなわち、元のイテレータが発生させる値のうち、`filter`に渡した関数が`true`を返すもの[^bool_conversion]のみを発生させるイテレータを返します。

[^bool_conversion]: 正確には「真偽値に変換すると`true`になる値」ですが、簡単のため省略しています。以降も同様です。

```js:filterメソッドの例
const arr = [0, 1, 1, 2, 3, 5, 8, 13, 21];
const iter1 = arr.values();
const iter2 = iter1.filter(x => x % 2 === 0);
for (const val of iter2) {
  console.log(val); // 0 2 8 が表示される
}
```

なお、当然ながら`map`や`filter`の返り値は新たなイテレータなので、さらにイテレータのメソッドを呼ぶことも可能です。いわゆるメソッドチェーンというやつですね。`map`と`filter`を組み合わせる例はこんな感じです。

```js:mapとfilterを組み合わせる例
const arr = [0, 1, 1, 2, 3, 5, 8, 13, 21];
const iter1 = arr.values();
const iter2 =
  iter1
    .filter(x => x % 2 === 0)
    .map(x => x * 10);
for (const val of iter2) {
  console.log(val); // 0 20 80 が表示される
}
```

このような使い方をする場合にイテレータの利点が少し出ています。イテレータを使わない場合は以下のようにすると同じことができますが、下の場合は`map`や`filter`を呼ぶたびに配列ができており、最終結果以外は使わないので無駄です。一方イテレータの場合に作られるのは新しいイテレータオブジェクトであり、イテレータは計算が遅延されるという特徴のため無駄な中間結果は作られません。

```js:イテレータを使わない例
const arr = [0, 1, 1, 2, 3, 5, 8, 13, 21];
const arr2 =
  arr
    .filter(x => x % 2 === 0)
    .map(x => x * 10);
for (const val of arr2) {
  console.log(val); // 0 20 80 が表示される
}
```

### `take`

`take`メソッドは引数として数値`limit`を受け取り、「元のイテレータの最初の`limit`個の値だけを出力するイテレータ」を返すメソッドです。下の例では`iter2`は5個値を発生させた時点で終了します。

```js:takeメソッドの例
const arr = [0, 1, 1, 2, 3, 5, 8, 13, 21];
const iter1 = arr.values();
const iter2 = iter1.take(5);
for (const val of iter2) {
  console.log(val); // 0 1 1 2 3 が表示される
}
```

このメソッドの偉い点は、無限イテレータを打ち切ることができるという点です。上のほうで例に出てきたフィボナッチ数列のイテレータは無限イテレータですが、`take`メソッドで途中で打ち切ることで有限イテレータに変換できます。

```js:takeメソッドの例
const iter = fib().take(10);
for (const val of iter) {
  console.log(val); // 0 1 1 2 3 5 8 13 21 34 が表示される
}
```

### `drop`

`drop`は、元のイテレータの最初のいくつかの要素を捨てる（無視する）ようなイテレータを作ります。

```js:dropメソッドの例
const iter = fib().drop(5).take(5);
for (const val of iter) {
  console.log(val); // 5 8 13 21 34 が表示される
}
```

この例では、`fib().drop(5)`は`fib()`の結果である無限イテレータの最初の5個を捨てるイテレータです。すなわち、`5 8 13 21 34 55...`という無限イテレータになります。それを`take(5)`で最初の5個だけにしているため上のような結果になります。

イテレータを用途に合わせていい感じに調整したいときに使えることがあります。

### `asIndexedPairs`

これは唯一見慣れない名前かもしれません。Rustでは`enumerate`と呼ばれているあれです。正直名前が微妙な気がするので`enumerate`とかにして欲しいと個人的には思っているのですが、逆に`enumerate`から`asIndexedPairs`に改名された形跡があるので難しいかもしれません。

これは、イテレータの出力を`[インデックス, 値]`というペアに変換する新しいイテレータを返します。つまり、配列の`entries`メソッドのイテレータ版です。このイテレータから最初に出力される値のインデックスは`0`となり、その次は`1`、さらにその次は`2`……となります。

次の例では、さっきの`fib().drop(5).take(5)`をさらに`asIndexedPairs()`で変換してから使っています。これにより`iter`の各出力は`[0, 5]`のような配列になっています。

```js:asIndexedPairsメソッドの例
const iter = fib().drop(5).take(5).asIndexedPairs();
for (const val of iter) {
  console.log(val); // [0, 5] [1, 8] [2, 13] [3, 21] [4, 34] が表示される
}
```

### `reduce`

ここまではイテレータを別のイテレータに変換するメソッドでしたが、ここからはイテレータを“消費”するメソッドです。

イテレータの`reduce`メソッドは配列の`reduce`のイテレータ版であり、配列を前から順番に処理する代わりにイテレータの値を順番に取り出して使用します。

このメソッドを呼び出した時点でイテレータの値は最後まで取り出されます。以降で紹介するメソッドも同じですが、無限イテレータに対してこのメソッドを呼び出した場合は無限ループに陥ってしまうので注意しましょう。

```js:reduceの例
const iter = fib().drop(5).take(5);
const sum = iter.reduce((a, b) => a + b, 0);

console.log(sum); // 81 (=5+8+13+21+34)
```

### `toArray`

`toArray`はイテレータの内容を全部読んで配列に詰めて返すメソッドです。`iter.toArray()`は`Array.from(iter)`と同じですね。

当初は`collect`という名前だったという説がありますが、どうやら今は`toArray`という名前で検討されているようです。

### `forEach`

`forEach`は配列の`forEach`のイテレータ版です。すなわち、イテレータの出力を順番に取り出してその値を引数にコールバック関数を呼び出すという動作をします。

```js:foreachの例
fib().drop(5).take(5).forEach(value => {
  console.log(value); // 5 8 13 21 34 が表示される
});
```

`for-of`文でも同じことができますのでまあ好みに合わせて使いましょう。`for-of`文に比べるとメソッドチェーンだけで完結するというメリットがあります。

### `every`

これも配列の`every`のイテレータ版です。イテレータの出力が順番に関数に渡され、全ての値に対してコールバック関数が`true`を返した場合は`every`の返り値が`true`となり、そうでなければ`false`となります。

```js:everyの例
console.log(fib().drop(5).take(5).every(x => x % 2 === 0)); // false
```

### `some`

配列の`some`のイテレータ版です。イテレータの値どれか一つに対して条件が成り立てば`true`が返ります。

### `find`

配列の`find`のイテレータ版です。すなわち、イテレータが出力する値を順番に見ていき、関数で与えられた条件を満たす値が出てきたらその値を返します。イテレータを最後まで読んでも条件を満たす値が存在しなかった場合は`undefined`が返ります。

```js:findの例
const firstEvenValue = fib().drop(5).find(x => x % 2 === 0);

console.log(firstEvenValue); // 8
```

以上で今回のプロポーザル「Iterator Helpers」に（今のところ）含まれているメソッドは終わりです。だいたい配列にもあるやつでしたね。

## イテレータを変換するメソッドに関する注意

この記事の前半で紹介したいくつかのメソッドはイテレータを変換するものでした。例えば`const iter2 = iter1.map(func)`として作ったイテレータが出力する値は、`iter1`が出力する各値に関数`func`を噛ませた値になります。

より正確に言えば、こうして作った`iter2`の`next`メソッドを呼び出した場合、内部的には`iter1`の`next`メソッドが呼び出され、その結果が`func`で加工されることになります。ということは、`iter1`と`iter2`を同時に扱うと次のようにややこしいことが起こります。

```js
const iter1 = [0, 1, 2, 3, 4, 5].values();
const iter2 = iter1.map(x => x * 10);

for (const value of iter2) {
  console.log(value);
  iter1.next();
}
```

これを実行すると、表示されるのは`0` `20` `40`の3つのはずです。`for-of`文でまず`iter2.next`が呼ばれて、内部的に`iter1.next`が呼ばれます。`iter1.next`の返り値は`0`なので`iter2.next`の返り値も`0`となり、`value`に`0`が入ります。その後、`for-of`文の中に書かれた`iter1.next()`によって`iter1`から値`1`が取り出されます。

そして`for-of`文の次のループに入り、再び`iter2.next`が呼ばれます。内部的に`iter1.next`が呼ばれるとその返り値は`2`であり、よって`iter2.next`の返り値が`20`となりそれが`values`に入ります。よって、`0`の次に`console.log`で表示されるのは`20`となります。

このような使い方はあまりすべきではありませんが。できてしまうので注意しましょう。（余談ですが、Rustとかはこういうのが出来ないようになっていて偉いですね。）

## 非同期イテレータへのメソッド追加

ところで、JavaScriptには**非同期イテレータ**という概念もあります。非同期イテレータは、普通のイテレータと似ていますが`next`メソッドを呼び出すと`{ done: false, value: 123 }`のような結果オブジェクトの代わりにそのようなオブジェクトに解決される`Promise`を返すオブジェクトです。

非同期イテレータは、`async function*`で宣言されるasyncジェネレータ関数を用いるなどの方法で作ることができ、`for-await-of`文でループすることができます。

この記事で紹介している「Iterator Helpers」プロポーザルでは、非同期イテレータに対してもメソッドを追加するとされています。ただ、まだ仕様等の記述が不完全でありその全容がどうなるのかはよく分かりませんでした。推測ですが、普通のイテレータにあるメソッドは非同期イテレータにも用意されるのではないかと思います。

ただし、非同期ならではの機能も追加されそうな雰囲気がしています。例えば、非同期イテレータの`map`メソッドに渡す関数は、返り値としてPromiseを返すことができます。

これ以上の詳細については不明なので続報を待ちましょう。

## まとめ

この記事ではJavaScriptのイテレータにメソッドを追加するプロポーザル「[Iterator Helpers](https://github.com/tc39/proposal-iterator-helpers)」の内容を今分かる範囲で紹介しました。実際に利用可能になるまでにはまだ変化があるかもしれませんので、温かい目で見守りましょう。

このあたりが入ると今よりはイテレータが使われるようになるかもしれませんから、イテレータのイの字も分からないという方は今のうちに勉強しておくのが吉です。何かイテレータが強くて使いやすい言語をやるのもいいですね。Rustとか。

記事を読んだ方には「メソッド少なすぎ😓<font color="#ff8844">（ここに好きなメソッド名を入れよう）</font>すらないとかJavaScriptクソだな😅」などといった感想を覚えた方も多いかと思います。ECMAScriptの標準化の仕組み上機能追加は最大公約数的になりがちであるという言い訳はしつつ、筆者もまあ「`takeWhile`くらいは入れてくれないと何もできなくない？　バカなの？」とか思っていないでもありませんのでまあ否定はしません。そのような意見はぜひ自由に触れ回りましょう。

ご意見ご感想をお待ちしています。