by shigemk2

当面は技術的なことしか書かない

PHP5.5新機能「Generator」初心者入門 #phpcon2012

ジェネレータ

  • ジェネレータ セーブ機能
  • ジェネレータ関数 シナリオ
  • ジェネレータオブジェクト セーブデータ
  • 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. 多重ネスト → コールバック関数を数珠繋ぎ → ジェネレータ

非同期処理が自然な形で記述できる

ジェネレータの落とし穴

break
呼び出し側でbreakされると、終了処理が行われない

リファクタリング
ジェネレータ関数を呼び出しただけではジェネレータは使えない