PHPのジェネレータまとめ
PHP5.5から使えるようになったジェネレータについて、社内普及用にまとめる。
ジェネレータとは
ジェネレータは、プログラムにおいて、数列の各要素の値などを次々と生成(ジェネレート)し他の手続きに渡す、という機能を持っている手続きである。値を渡す方法としては、コールバックのようにして他の手続きを呼ぶものもあれば、呼び出される度に次々と異なる値を返す関数であることもある。
ジェネレータ (プログラミング) - Wikipedia
ジェネレータを使えば、シンプルな イテレータを簡単に実装できます。 Iterator インターフェイスを実装したクラスを用意する オーバーヘッドや複雑さを心配する必要はありません。
PHP: ジェネレータとは - Manual
シンプルな例
<?php // 二次元配列 $num_lists = [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]; // 二次元配列を二重のループで展開 ================== foreach ($num_lists as $nums) { foreach ($nums as $num) { echo "\$num is $num.\n"; } } // ジェネレータを使う ============================== // ジェネレータ関数に二重のループ部分を押し込む function flatten($num_lists) { foreach ($num_lists as $nums) { foreach ($nums as $num) { yield $num; } } } // フラットに値が返ってくるので素直にループ foreach (flatten($num_lists) as $num) { echo "\$num is $num.\n"; }
Iteratorインタフェースが実装されたGeneratorオブジェクト
yieldはGeneratorオブジェクトを返却している。
GeneratorオブジェクトはIteratorインタフェースが実装されている。
なので、苦労してcurrent(), key(), next(), rewind(), valid()を実装する必要がない。
ただし、Iteratorと違って、rewindを使って巻き戻すことができず前にしか進めない。
戻したければ再度yiledを呼び出して、Generatorオブジェクトを作る必要がある。
<?php $g = flatten($num_lists); echo $g->current()."\n"; // 1 $g->next(); echo $g->current()."\n"; // 2 $g->next(); echo $g->current()."\n"; // 3 // $g->rewind(); エラー! // でもまた1に戻したい! $g2 = flatten($num_lists); echo $g2->current()."\n"; // 1に戻った!
ジェネレータから値を送る
send()メソッドで値をyield側に送れる。
<?php function flatten2($num_lists) { $sum = 0; foreach ($num_lists as $nums) { foreach ($nums as $num) { $sum += (yield $num); // 要括弧 } } echo "\$sum is $sum."; } $g = flatten2($num_lists); foreach ($g as $num) { $g->send($num * 2); }
yieldは括弧で囲う必要がある。
また、ジェネレータ側のfunctionでのreturnは受け取れない。
PHP7のRFCとして、getReturnがあるようだ。
PHP: rfc:generator-return-expressions
<?php function flatten2($num_lists) { $sum = 0; foreach ($num_lists as $nums) { foreach ($nums as $num) { $sum += (yield $num); } } return $sum; // ジェネレータ自体の返却値 } $g = flatten2($num_lists); foreach ($g as $num) { $g->send($num * 2); } $sum = $g->getReturn(); // こうできればいいなぁ echo "\$sum is $sum.";