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の制御などが不要になり、シンプルに記述することが出来ました。