ES2015新機能: ジェネレータをマスターしたい


Koaをやるにあたって、やっぱりジェネレータを知らなければ話にならないようです。
「ジェネレータを使ってコールバックを使わずに非同期処理できる!」と聞いても、「なんでやねん。。。」としかならなかったので、ここは一つ、腰を据えてジェネレータについて勉強しておきたいところです

# ジェネレータとは

## ジェネレータの意味

ジェネレータは「生成器」なんて訳されていますが、プログラムの世界では次々と値を生成する仕組みのことのようです。

もう少し狭義の世界で考えると、イテレータを実現する仕組みの一種となっていたりもします。
PHPにも5.5以降はジェネレータの仕組みが有りますが、そこでは「イテレータを簡単に実装できる仕組み」となっています。
Javascript の解説ページなどを見ると、やはりイテレータとセットになっている所も多いようです

ですが、どちらかと言うとジェネレータはあくまで値を次々生成する仕組みであり、機能の一部としてイテレーションが入っているように思います

## ジェネレータの実装

ジェネレータを早速実装してみましょう

```
'use strict'

// ジェネレータの定義
function* MakeNumber() {
  yield 1;
  yield 2;
  yield 3;
}

let gen = MakeNumber();// ジェネレータの初期化

console.log(gen.next());// { value: 1, done: false }
console.log(gen.next());// { value: 2, done: false }
console.log(gen.next());// { value: 3, done: false }
console.log(gen.next());// { value: undefined, done: true }
```

ジェネレータの定義方法は、`function`の代わりに`function*`で定義し、定義の中で幾つかの`yield`が含まれています。
次にジェネレータを初期化し変数`gen`をジェネレータにします。
ジェネレータ`gen`は`next`メソッドを持っています。
`next`の役割は以下のとおりです

1. ジェネレータ関数の定義の現在位置から`yield`のある位置まで処理をすすめる
2. `yield`キーワードの後に書かれた式を返却値とし、処理位置をそこで止める
3. 正確な返却値は`{value: <yieldキーワードの後に書かれた式>, done: false}`となる
4. 後ろにyieldがない状態でnextを呼ぶと、{value: undefined, done: true}を返す

つまり、`next`メソッドを呼ぶたびに、各`yield`まで処理が進み、一旦値を返すという処理が繰り返されます。
最後の`yield`の処理が終わると、もう値を返せないので以降の`next`は`value`がなしで(`value: undefined`)、ジェネレータの処理が完了しているサイン(`done: true`)を返します。

## 無限生成

ループと`yield`を組み合わせることで、無限に生成し続けるジェネレータを作ることができます

```
'use strict'

// 階乗計算
function* Factorial() {
  let i = 1;
  let num = 1;
  while (true) {
    num *= i++
    yield (i - 1) + '! = ' + num;
  }
}

let gen = Factorial();// ジェネレータの初期化

console.log(gen.next());// { value: '1! = 1', done: false }
console.log(gen.next());// { value: '2! = 2', done: false }
console.log(gen.next());// { value: '3! = 6', done: false }
console.log(gen.next());// { value: '4! = 24', done: false }
```

これはいくら`next`を呼びつづけても、延々と値を生成し続けますので、`done`が`true`になることはないでしょう

## イテレータとジェネレータ

イテレータの反復処理の対象としてジェネレータを選択することができます。

```
'use strict'

// 階乗計算
function* Factorial() {
  let i = 1;
  let num = 1;
  while (i < 7) {
    num *= i++
    yield {number: (i - 1), fact: num}
  }
}

let gen = Factorial();// ジェネレータの初期化

// 反復処理の対象としてジェネレータを選択する
for (let obj of gen) {
  console.log(obj.number + '! = ' + obj.fact);
  if (obj.number >= 10) {
    break;
  }
}
```

結果はこんな感じです

```
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
```

まず、反復対象としてジェネレータを選んだ場合、各要素`obj`にはは'next'メソッドで返却されるオブジェクトの`value`が代入されることがわかります。
続いてfor文の中では`obj.number`が10になるまではループを続けられるようになっていますが、実際には`obj.number`は6までで打ち止めになっています。
これは`next`メソッドで返却されるオブジェクトの`done`が`true`になっていると、反復処理が終了することを意味しています。

## ジェネレータの中のジェネレータ

`yield*` を使うことで、ジェネレータの中で別のジェネレータに処理を移譲することもできます。

```
'use strict'

// 入れ子のジェネレータ
function* Subgen() {
  yield 3;
  yield 4;
}

function* Maingen() {
  yield 1;
  yield 2;
  yield* Subgen();
  yield 5;
}

let gen = Maingen();// ジェネレータの初期化

for (let num of gen) {
  console.log(num);
}
```

結果は以下のとおりです

```
1
2
3
4
5
```

この`yield*`は、ジェネレータにかぎらず反復処理の対象にできるものであれば何でも指定できるので、例えば`yield* Subgen()`の部分を`yield* [3, 4]`に書き換えても、全く同じ動作をします。

## yieldの「返り値」

`yield`はこれまで見てきたように、ジェネレータの中ではその処理を中断し、右にある式の値を返します。
では、再度`next`を呼び出して処理を行うとき、どこから始まるのでしょうか？
答えは簡単で、`yield`から始まります。
しかも、このとき、`yield`には`next`メソッドに与えられた引数が展開されます。
次のコードを見てください

```
'use strict'

function* Maingen() {
  while (true) {
    let x = yield 1;
    console.log(x);
  }
}

let gen = Maingen();

gen.next();// <何も出力されない>
gen.next();// undefined
gen.next(1);// 1
gen.next();// undefined
gen.next('A');// A
```

一回目の`next`では、そもそも`console.log`を通過する前に止まってしまうので、何も出力されません。
続いて、二回目の`next`では引数に何も指定していないため、ジェネレータ内部の`x`には何も代入されません。したがってundefinedが出力されます。
三回目の`next`には1が引数として与えられています。すると、`yield`に1が入り、`x`に1が代入されます。したがって、`console.log`によって1が出力されるというわけです。
その後も内容は同じです。四回目は何も指定していないので、undefinedになり、五回目は'A'を指定したので、Aが出力されます。

# まとめ

ES2015の目玉機能であるところのジェネレータについて見てみました。
このジェネレータが非同期処理とどう絡むかについて、次回見てみたいと思います。

# 参考

[イテレータとジェネレータ - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Iterators_and_Generators)
[PHP: ジェネレータとは - Manual](http://php.net/manual/ja/language.generators.overview.php)