PHP5.5で追加されたジェネレータについて調べた
前にまとめたPHP5系で追加された機能を振り返る記事の続き。今回は、ほぼ知らなかったジェネレータについて調べた。サンプルコードを多めに載せ、まとめていく。
ジェネレータとは
ジェネレータとは、イテレータのような反復処理をシンプルに実装できる構文である。
例えば、range()
関数をジェネレータで実装し直すと以下のようになる。
<?php function xRange($start, $limit, $step = 1) { for ($i = $start; $i <= $limit; $i += $step) { yield $i; } } foreach (xRange(1, 10, 2) as $number) { echo "{$number}\n"; // 1 3 5 7 9 }
通常のrange()
関数の場合、指定された範囲の値を含む配列を返すため、配列のサイズ分メモリを使用してしまうが、xRange()
関数のようなジェネレータ関数を実装することでメモリの節約をすることができる。
ジェネレータの構文について
yield
キーワード
ジェネレータ関数で最も重要となるのが、このyield
キーワードである。return
のように値を返して関数の処理が終了するのではなく、yield
はジェネレータ関数の呼び出し元に値を返し、そこでジェネレータ関数の実行を一時停止する。上記のxRange()
関数のように、ジェネレータ関数内でループ中であっても、処理を一時停止し、再びループを再開することができる。
また、yield
は以下のようなキーと値のペアを返すこともできる。
<?php function getRandomValue() { $counter = 0; while (true) { yield ++$counter => rand(0, 10); } } foreach (getRandomValue() as $key => $value) { echo "{$key}: {$value}\n"; if ($value == 0) { break; } }
Generatorクラスについて
ジェネレータ関数が最初に呼び出されたとき、Generatorクラスのオブジェクトを返す。このオブジェクトはIteratorインターフェイスを実装していて、このオブジェクトのメソッドを使用することで、ジェネレータを操作することができる。
例えば、上のgetRandomValue()
関数の呼び出しは以下のように書き換えることもできる。
<?php $rnd = getRandomValue(); while (true) { $key = $rnd->key(); // yieldしたキーを取得 $value = $rnd->current(); // yieldした値を取得 echo "{$key}: {$value}\n"; if ($value == 0) { break; } $rnd->next(); // ジェネレータを続行する }
Generatorクラスは他にも便利なメソッドがあるが、それらについては後述する。
インタラクションについて
Generatorクラスにはsend()
メソッドが備わっている。このメソッドは、指定した値をyield
の結果としてジェネレータに送ることができる。これを用いることで、双方向で値を受け渡しすることができ、例えば、以下のような数あてゲームのようなことができるようになる。
<?php $ng = (function ($ans) { $input = yield; while ($input != $ans) { if ($input < $ans) { $input = yield "\$ans > {$input}"; } else { $input = yield "\$ans < {$input}"; } } })(rand(0, 100)); echo "Start\n"; do { fscanf(STDIN,'%d', $ans); $hint = $ng->send($ans); echo ($hint ?: 'Correct!!') . PHP_EOL; } while ($hint);
PHP7.0から追加された機能
yield from
キーワード
このキーワードを用いることで、別のジェネレータやTraversableオブジェクトや配列から順に値を取り出し、それぞれの値をyield
することができる。
<?php function f1() { yield 1; yield from [2, 3]; yield from f2(); yield 6; } function f2() { yield 4; yield 5; } foreach(f1() as $value){ echo "{$value}\n"; // 1 2 3 4 5 6 }
return
キーワード
PHP5まではジェネレータ関数内でreturn
できなかった。return
されたタイミングでジェネレータ関数は終了し、このreturn
の戻り値は通常のreturn
のように値を受け取るのではなく、GeneratorクラスのgetReturn()
メソッドで取得することができる。
<?php function getNumbers() { for ($i = 0; $i < 10; ++$i) { yield $i; } return "Finish!!"; } foreach ($gen = getNumbers() as $value) { echo "{$value}\n"; } echo $gen->getReturn() . PHP_EOL;
所感
ジェネレータをうまく活用しているのをこれまでほとんど見たことがないので、どのように活用すれば良いかパッと思いつかないが、PHPの機能としてはとても面白いと思った。