embryo

エンジニアの備忘録

es6でカスタムイテレータを作る (Iterator, Iterable, Generator)

今日はes6の仕様であるIteratorIterableについてです。仕様を調べつつ実装して挙動を確かめていきます。

Iterator

一連の処理中において現在の処理位置を把握しつつ、コレクション中の項目へ一つずつアクセスする方法を指します。es6においては下記のnextメソッドを実装したものを指します。

※実際に下記のようなIteratorクラスが定義されているわけではありません。

class Iterator {
     next(): { done: boolean, value: any }
}

例えば以下のような実装はiteratorになります。

let iterator = {}
iterator.next = () => {
     return { done: false, value: 1 }
}

console.log(iterator.next().value) // 1
  • doneIteratorが完了したかを返す真偽値
  • valueIteratorから取り出した値

であり、donetrueで返ってくる場合、valueは省略されます。

Iterable

iteratorプロトコルとは別にiterableプロトコルというプロトコルが定義されています。iterableプロトコルを実装したオブジェクトはfor … of文が使用可能になります。

for … of 文はパラメータを列挙するオペレータで以下のように使用できます。

let iterable = [1, 2, 3]
for (item of iterable) {
     console.log(item)
}

// 1
// 2
// 3

具体的には下記の[Symbol.Iterator]メソッドを実装したものを指します。また返却値は前述したIteratorプロトコルになります。

class Iterable {
     [Symbol.Iterator](): Iterator
}

例えば以下のような実装はIterableになります。

let iterable = {}
iterable[Symbol.iterator] => {
     return next() => {
          return { done: false, value: 1 }
     }
}

Linked List構造のIterableを作る

Iterableプロトコルを実装することで、ビルトインのiterable以外のものを自前で実装可能であることがわかりました。 そこでLinkedListの構造を持ったIterableオブジェクトを作って試していきます。LinkedListの実装は下記に公開しています。

LinkedList.js · GitHub

class IterableLinkedList extends LinkedList {

  [Symbol.iterator]() {

    let node = this.head;

    return {

      next: () => {

        if (!node) {
          return {
            done: true
          };
        }

        let value = node.value;

        node = node.next;

        return {
          value: value,
          done: false
        };

      }

    };

  }

}

自身でカスタムイテレータを作る場合、やはり内部のコードは煩雑になってしまいますね。またIterable, Iteratorプロトコルを正しく理解していなければなりません。そこで次は同じくes6の仕様であるGeneratorを使って同じものを実装してみます。

Generatorを使用した実装

Generator関数はIteratorプロトコルに準拠しているため、Generator関数を用いることでよりシンプルにIterableを実装することが可能になります。メソッドに定義する場合は関数名の先頭に*をつけることでGenerator関数が私用可能になります。

class IterableLinkedList extends LinkedList {

    * [Symbol.iterator]() {

      let node = this.head;

      while (node) {
        yield node.value;
        node = node.next;
      }

    }

}

Generatorを使用することでnextメソッドの定義や、doneの制御などが不要になり、シンプルに記述することが出来ました。

参考