TypeScript: for-ofでループできるクラスを定義する

本稿では、TypeScriptにて`for ( ... of ... )`でループできるクラスの作り方を説明する。

## やりたいこと

やりたいこととしては、次のような自作のクラスがあって、

```ts
class MyList {
  constructor(private elements: Array<number>) {}
}
```

これを`new`したオブジェクトを、

```ts
const list = new MyList([1, 2, 3])
```

まるで`Array`のように`for (... of ...)`でループできるようにしたい。

```ts
for (const element of list) {
  // ...
}
```

もちろんこのままじゃコンパイルエラーになります。

## JavaScriptのオブジェクトはどういう基準でループできるのか？

まず、JavaScriptが`for-of`でループできるものが何なのか、知っておく必要がある。

* [反復処理プロトコル - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols)

ビルトインの`Array`や`Map`は、「反復処理プロトコル(itetration protocol)」に準拠したオブジェクトになっているためループできる。

つまり、自前のオブジェクトでも「反復処理プロトコル」に準拠してさえいれば、ループできるというわけである。

## 反復処理プロトコルを自前クラスに組み込む

反復処理プロトコルを自前クラスに組み込むには、`[Symbol.iterator]()`というメソッドを生やせば良い。

```ts
class MyList {
  constructor(private elements: Array<number>) {}
  [Symbol.iterator]() {}
}
```

このメソッドの戻り値の型は`Iterator<T>`にならないといけない。

```ts
class MyList {
  constructor(private elements: Array<number>) {}
  [Symbol.iterator](): Iterator<number> {}
}
```

`this.elements`は`Array<number>`だが、先述したとおり`Array<T>`も反復処理プロトコルを実装しているので、`[Symbol.iterator]`メソッドを持っている。

つまり、次のように、`this.elements[Symbol.iterator]()`の戻り値をそのまま`return`してしまえば、コンパイルが通るようになる。

```ts
class MyList {
  constructor(private elements: Array<number>) {}
  [Symbol.iterator](): Iterator<number> {
    return this.elements[Symbol.iterator]()
  }
}
```

最後に、クラス自体も`Iterable<number>`を`implements`しておくといい。無くても動くが、コンパイル時にメソッドのシグネチャのチェックがなされるので、安心度が高まる。

```ts
class MyList implements Iterable<number> {
  constructor(private elements: Array<number>) {}
  [Symbol.iterator](): Iterator<number> {
    return this.elements[Symbol.iterator]()
  }
}
```

このコードは試しに[TypeScript playground](http://www.typescriptlang.org/play/?target=3#code/MYGwhgzhAECyCeAZAlhALtZBbADiAplvgHZowCSa+ATmAEYEA8xArlnTQHzQDeAUNGjAA9sXTUWwNMOoAKHNWQA3MFWj4CRUhABc0AILVa8Zmw7VOASl4BfAdADaAZXjthIAHTIqtadQC6spZ6lDSqMqbsXLz2gtT4aCzUxNBoABaoHhqEJGTOrnTuXj7hAUH2dnZ8ImIYIKgYALzQxPgA7nBIDbIOAIwANNAATIMAzP6WfHwAZjLQsjXo6pq50MLT0PXo1vyCi+74HiDCAOay2VpokzZAA)で動かしてみることができる。

## 込み入った実装

上記では話をシンプルにするために`Array<T>`の`[Symbol.iterator]`メソッドを呼んで返すだけの実装にしたが、自前クラスに反復処理プロトコルを実装する背景には、複雑なループを隠蔽したいことがあると思う。

そこで、もう少し込み入ったイテレータの実装をサンプルとして考えてみる。

次のサンプルは、偶数だけをループする実装になる:

```ts
class MyList implements Iterable<number> {
  constructor(private elements: Array<number>) {}
  *[Symbol.iterator](): Iterator<number> {
    for (const element of this.elements) {
      if (element % 2 == 0)
        yield element
    }
  }
}

const list = new MyList([1, 2, 3, 4, 5, 6])

for (const element of list) {
  console.log(element) // 2, 4, 6のみが出力される
}
```

このコードは[TypeScript Playground](http://www.typescriptlang.org/play/?target=3#code/MYGwhgzhAECyCeAZAlhALtZBbADiAplvgHZowCSa+ATmAEYEA8xArlnTQHzQDeAUNGjAA9sXTUWwNMOoAKHNWQA3MFWj4CRUhABc0AILVa8Zmw7VOASl4BfAdABUAbQDK8dsJAA6ZFVrTqAF1ZSz1KGlUZU3YuXntBADMZaFkRMQwNQhIMYQToNAALVC9MrTJrfkEqzDzZUuzoAFJoACZoAF526AAGS3jq6HhkDQATdU1s-rtBOzs+NPRoEFQMLuJ8AHc4JBXZJwBGABpW44BmY4AWY4BWY4A2QL6+JOoUhYyJ0mhcpZWK+wWnnwXhAwgA5nVPmg+jYgA)で動かしてみれる。

`[Symbol.iterator]`メソッドの頭についている`*`はジェネレーター関数のマークで、`yield`が使えるようになる。ジェネレーターはTypeScriptでは

```ts
interface Generator extends Iterator<any> { }
```

と定義されており、イテレータとして扱うことができる。