LoginSignup
0
1

More than 1 year has passed since last update.

イテレーターのクローンもどき

Posted at

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 というライブラリが紹介されています。

これは値をキャッシュすることでイテレーターをコピーする方法です。リストモナドの例のように値が変わる場合には使えません。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1