JavaScript のジェネレーターでは実行途中のイテレーターをクローンできません。無理やりそれっぽいことをしてみました。
実装
next()
に渡す引数をキャッシュして、毎回最初からやり直すことで強引にクローンのように見せかけます。
function makeCloneableIterator(g) {
const it = g();
const args = [];
return {
next(arg) {
args.push(arg);
return it.next(arg);
},
clone() {
const ret = makeCloneableIterator(g);
args.forEach(ret.next);
return ret;
}
};
}
使用例
途中から再開しているように見えます。
const it = makeCloneableIterator(function*(){
yield 1;
yield 2;
return "return";
});
console.log(it.next());
const it2 = it.clone();
console.log(it.next());
console.log(it.next());
console.log(it2.next());
console.log(it2.next());
実行結果
{ value: 1, done: false }
{ value: 2, done: false }
{ value: "return", done: true }
{ value: 2, done: false }
{ value: "return", done: true }
リストモナド
イテレーターをコピーできないことは、ジェネレーターでリストモナドを模倣する際に障害になっていました。
今回の実装を使えばリストモナドも模倣できます。
function* ListMonad(g) {
function* f(it, arg) {
const r = it.next(arg);
if (r.done) {
yield r.value;
} else {
for (const v of r.value) {
const it2 = it.clone();
yield* f(it2, v);
}
}
}
yield* f(makeCloneableIterator(g));
}
使用例
Haskell のコードを移植してみます。
main =
print $ do
x <- [1, 2, 3]
y <- [x * 10, x * 100, x * 1000]
return $ x + y
実行結果
[11,101,1001,22,202,2002,33,303,3003]
See the Pen Cloneable Iterator by 七誌 (@7shi) on CodePen.
※ 出力の log()
は次の実装を使っています。
経緯
これまでリストモナドを模倣するのに for
を使ったり、強引に CPS 変換をしたりしていました。
今回の実装はあくまで実験的なもので、for
を使うのが素直な気はします。
参考
回答で forkable-iterator というライブラリが紹介されています。
これは値をキャッシュすることでイテレーターをコピーする方法です。リストモナドの例のように値が変わる場合には使えません。