javascriptのジェネレータ関数と遅延評価

ES6で追加された文法としてジェネレータ関数というものがあります。

ジェネレータ関数とは

ジェネレータ関数とは、処理を離脱したり復帰したりできる関数であり、大雑把にいえばイテレータ(ジェネレーター)を生成する関数です。

具体的には、function*yieldを用いて以下のように定義します。

function* gen() {
  console.log('first');
  yield;
  console.log('secound');
  yield;
  console.log('third');
}

この関数を呼び出すことで、ジェネレーターと呼ばれる、イテラブルなオブジェクトが得られます。

var g = gen();
g.next();  // コンソールに'first'と出力される
g.next();  // コンソールに'secound'と出力される
g.next(); // コンソールに'third'と出力される

見てわかるとおり、yieldキーワードで関数を一時離脱する地点を定義しています。

これを利用すると、たとえばフィボナッチ数を順に返すジェネレーターは次のように定義できます。

function* fib_gen() {
  var a = 0, b = 1;
  while(1) {
    console.log(a);
    yield;
    [a, b] = [b, a + b];
  }
}

var fib = fib_gen();
fib.next(); // 0
fib.next(); // 1
fib.next(); // 1
fib.next(); // 2
fib.next(); // 3

また、next(){value: 'hogehoge', done: false}のような構造のデータを返却します。このとき、valueyieldキーワードによって返された値、doneは続く処理が残っていないか否か(処理が残っている場合にはfalse、すべて終了した場合にはtrue)を表します。

function* fib_gen() {
  var a = 0, b = 1;
  while(1) {
    yield a;
    [a, b] = [b, a + b];
  }
}

var fib = fib_gen();
fib.next(); // {value: 0, done: false}
fib.next(); // {value: 1, done: false}
fib.next(); // {value: 1, done: false}
fib.next(); // {value: 2, done: false}
fib.next(); // {value: 3, done: false}

ジェネレーターと遅延評価

このジェネレーターという仕組みを利用すると、遅延評価される関数を定義することができるようになります。

ジェネレーター関数はジェネレーターを返す関数ですが、これは見方を変えればnext()が実行されるまでは評価が遅延される関数なのだと考えることもできます。そこで、

function* heavy() {
    // すっごく重い処理
    yield '返り値';
}
var res = heavy();

とすることで、関数の遅延評価に近いことをすることもできます。

同様の発想で、ジェネレーター関数を利活用することで、コールバック地獄から逃げ出すことができます

参考