ES2015 の iterable protocol / iterator protocol だとそこそこ自然に無限リストを作れるわけなので、ちょっと試しにやってみました。node v5.2.0 で動かしました。
"use strict";
function* countup(n) {
for (;;) yield n++;
}
function* map(iterable, func) {
for (let value of iterable) {
yield func(value);
}
}
function* cycle(iterable) {
for (;;) {
for (let value of iterable) {
yield value;
}
}
}
function take(iterable, n) {
const ret = [];
for (let value of iterable) {
ret.push(value);
if (!(ret.length < n)) break;
}
return ret;
}
function* zip(iterable) {
for (;;) {
const nexts = [];
for (let it of iterable) {
nexts.push(it.next());
}
yield nexts.map( (i) => i.value);
if (nexts.some( (i) => i.done) ) break;
}
}
console.log(
take(
map(
zip([
countup(1),
cycle(["", "", "Fizz"]),
cycle(["", "", "", "", "Buzz"])
]),
(_) => _[1] + _[2] || _[0]
),
30
)
);
FizzBuzz の map のところは destructive assignment ができるともうちょい綺麗に書けますが、現時点だとまだオプションが必要なのでキモい書きかたになりました。
気になった点
protocol と言っている通り、next() を適切なインターフェイスで実装しているものは全て iterator なため、イテレータ全般に対して基底クラスみたいなものがありません。これはこれでいいんですが、不便な点があります。イテレータに対してメソッドの追加というのができません。
自分の中のオブジェクト指向の気持ちは以下のように書きたいのです。
[
countup(1),
cycle(["", "", "Fizz"]),
cycle(["", "", "", "", "Buzz"])
].
zip().
map( (_) => _[1] + _[2] || _[0] ).
take(30)
しかし、イテレータの prototype というのは存在しないので、毎回必ず何らかの関数形式のラッパーが必要になってしまいます。