ジェネレータ
- ジェネレータ セーブ機能
- ジェネレータ関数 シナリオ
- ジェネレータオブジェクト セーブデータ
- yield文 セーブポイント
<?php // 何回呼んでも0が返る function func () { $i = 0; return $i; $i++; return $i; $i++; return $i; }
<?php // yieldを使ったジェネレータ関数の例 // ジェネレータ関数 function gfunc () { $i = 0; // yieldは終了ではなく一時中断 yield $i; // 1回目は0 $i++; yield $i; // 2回目は1 $i++; yield $i; // 3回目は2 } // 前回の終了位置から実行を再開出来る // メインプログラム // オブジェクトを作成しなければならない。 // gfunc(); はNG $g = gfunc(); // foreachと共に実行しなければならない。 foreach ($g as $x) { // yield文の引数がループ変数になる var_dump($x); }
int(0)
int(1)
int(2)
ジェネレータ関数を無限ループにして、
メインプログラムで終了条件を指定するとよい。
ジェネレータが使えると何がうれしいの?
ファイルを1行ずつ処理
<?php function gfunc() { $f = fopen($filename, "r'); if ($f === false) throw ...; $line = fgets($f); while ($line !== false) { yield $line; } fclose($f); } $g = gfunc($filename); foreach($g as $line) { // 汎用性の低い処理を分離する $arr = explode("\t", $line); echo $arr[1], "\n"; }
<?php // ジェネレータオブジェクトを受け取り、新しい // 別のジェネレータオブジェクトを生成する function ggfunc($g) { foreach($g as $line) { // 汎用性の低い処理を分離する $arr = explode("\t", $line); yield $arr; } }
while($line !== false) { $lines[] = $line; // こんなかんじの配列作成はメモリを食いまくる $line = fgets($f); }
while($line !== false) { yield $line; // 配列を作成必要がなくなる $line = fgets($f); }
リダイレクト vs パイプライン
すべてを配列に格納する = リダイレクト
ジェネレータ = パイプ 巨大な中間ファイルが必要ない
ジェネレータの利点
- ループ処理から汎用性の高い箇所を切り出す 再利用性の向上
- ひとつの大きなループを複数の小さなループに分解する
- メモリ消費量が少ない
- データを読んだはしから処理できる ストリームデータも処理可能
どんな使い道があるのか?
双方向のやりとりが可能となる。
$g->send(); // 次のyield文まで実行する $value = $g->send("arg"); $arg = (yield "value");
<?php $g = gfunc(): $ret = $g->send(1); $ret = $g->send(2); $ret = $g->send(3); function gfunc() { $arg = yield; // sendメソッドの引数 while (...) { $ret = ...; $arg = (yield $ret); // sendメソッドの戻り値 var_dump($arg); } }
ジェネレータとマルチメソッド
マルチスレッドよりも低機能だが、ジェネレータを使うとメモリ消費量が極めて少なくなる (=大量生成が可能となる)
ジェネレータと非同期処理
1. 多重ネスト → コールバック関数を数珠繋ぎ → ジェネレータ
非同期処理が自然な形で記述できる