es6でカスタムイテレータを作る (Iterator, Iterable, Generator)
今日はes6の仕様であるIterator
、Iterable
についてです。仕様を調べつつ実装して挙動を確かめていきます。
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
であり、done
がtrue
で返ってくる場合、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の実装は下記に公開しています。
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の制御などが不要になり、シンプルに記述することが出来ました。