PHP5.5新機能「ジェネレータ」初心者入門

36,281 views
35,815 views

Published on

PHP5.5の新機能「ジェネレータ(Generator)」について、「それって何?」「どううれしいの?」「何に使えるの?」の3つを初心者向けに解説。動画 http://www.slideshare.net/kwatch/php55

Published in: Technology
0 Comments
97 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
36,281
On SlideShare
0
From Embeds
0
Number of Embeds
3,753
Actions
Shares
0
Downloads
122
Comments
0
Likes
97
Embeds 0
No embeds

No notes for slide

PHP5.5新機能「ジェネレータ」初心者入門

  1. 1. PHPカンファレンス2012PHP5.5新機能 かもしれないGenerator初心者入門makoto kuwata <kwa@kuwata-lab.com>http://www.kuwata-lab.com/2012-09-15 (Sat) copyright(c) 2012 kuwata-lab.com all rights reserved.
  2. 2. 本発表について【目的】 • PHP5.5の新機能かもしれない「ジェネレータ」を、 「なんだか凄そうだ」と思ってもらう。【内容】 • ジェネレータって何? • どううれしいの? • どんなことに使えるの?【注意】 • 内容は2012-09-15時点での情報に基づく。 今後、仕様変更があり得るので注意。 copyright(c) 2012 kuwata-lab.com all rights reserved.
  3. 3. ジェネレータって何?What is Generator? copyright(c) 2012 kuwata-lab.com all rights reserved.
  4. 4. まとめ◆ ジェネレータ セーブ機能◆ ジェネレータ関数 ゲームシナリオ◆ ジェネレータオブジェクト 冒険の書 (セーブデータ)◆ yield文 宿屋(セーブポイント) copyright(c) 2012 kuwata-lab.com all rights reserved.
  5. 5. 通常の関数 1: function func() { 2: $i = 0; 1, 2, 3回目 (0が返される) 3: return $i; 4: $i++; 5: return $i; 6: $i++; 7: return $i; 8: } 毎回先頭から実行され、また return文より後ろは実行されない copyright(c) 2012 kuwata-lab.com all rights reserved.
  6. 6. ジェネレータ (Generator) 関数 1: function gfunc() { 2: $i = 0; 1回目 (0が返される) 3: yield $i; 4: $i++; 5: yield $i; 2回目 (1が返される) 6: $i++; 7: yield $i; 3回目 (2が返される) 8: } 前回の終了位置から再開 copyright(c) 2012 kuwata-lab.com all rights reserved.
  7. 7. 使い方 ジェネレータオブジェクトを生成 (通常の関数と使い方が違うことに注意!) 1: $g = gfunc(); 2: foreach ($g as $x) { 3: var_dump($x); 4: } foreach文とともに使用 (イテレータとして振る舞う)実行例int(0)int(1)int(2) copyright(c) 2012 kuwata-lab.com all rights reserved.
  8. 8. 実行順序:ループ1回目メインプログラム ジェネレータ関数1 $g = gfunc(); function gfunc(){2 foreach($g as $x){ $i = 0; 3 var_dump($x); yield $i; 4 5 } $i++; echo "donen"; yield $i; $i++; return $i; } copyright(c) 2012 kuwata-lab.com all rights reserved.
  9. 9. 実行順序:ループ2回目メインプログラム ジェネレータ関数 $g = gfunc(); function gfunc(){6 foreach($g as $x){ $i = 0; var_dump($x); yield $i; 9 } $i++; 7 echo "donen"; yield $i; 8 $i++; return $i; } copyright(c) 2012 kuwata-lab.com all rights reserved.
  10. 10. 実行順序:ループ3回目…は、ない メインプログラム ジェネレータ関数 $g = gfunc(); function gfunc(){10foreach($g as $x){ $i = 0; var_dump($x); yield $i; } $i++; yield $i;13echo "donen"; $i++; 11 return $i; 12・ループのたびにyield文まで実行・yield文の引数がループ変数に } copyright(c) 2012 kuwata-lab.com all rights reserved.
  11. 11. サンプル:2つの値を交互に出力 1: function toggle($odd, $even) { 2: while (TRUE) { 3: yield $odd; 4: yield $even; 5: } 6: } 7: 8: // "red" と "blue" を交互に出力 9: foreach (toggle("red", "blue") as $c){10: echo $c, "n"; // 無限に出力11: } copyright(c) 2012 kuwata-lab.com all rights reserved.
  12. 12. サンプル:フィボナッチ数列 (0, 1, 1, 2, 3, 5, 8, 13,...) 1: function fib() { 2: $x = 0; $y = 1; コツ:ループの終了条件を 3: while (TRUE) { 指定しない (無限ループ) 4: yield $x; 5: list($x, $y) = [$y, $x+$y]; 6: } 7: } 8: 9: // 100未満のフィボナッチ数列を出力10: foreach (fib() as $x) {11: if ($x >= 100) break;12: echo $x, "n"; コツ:終了条件は呼13: } び出す側で指定する copyright(c) 2012 kuwata-lab.com all rights reserved.
  13. 13. まとめ◆ ジェネレータ セーブ機能◆ ジェネレータ関数 ゲームシナリオ◆ ジェネレータオブジェクト 冒険の書 (セーブデータ)◆ yield文 宿屋(セーブポイント) copyright(c) 2012 kuwata-lab.com all rights reserved.
  14. 14. どううれしいの?Why Generator is so useful? copyright(c) 2012 kuwata-lab.com all rights reserved.
  15. 15. Before: ファイルを1行ずつ処理する 1: // 行番号つきで表示 2: $f = fopen($filename, r); 3: if ($f === FALSE) throw ....; 4: $line = fgets($f); 5: while ($line !== FALSE) { 6: ++$i; 7: echo $i, ": ", $line; 8: $line = fgets($f); 9: }10: fclose($f);11: copyright(c) 2012 kuwata-lab.com all rights reserved.
  16. 16. Before: ファイルを1行ずつ処理する 1: // パターンで絞り込む 2: $f = fopen($filename, r); 3: if ($f === FALSE) throw ....; 4: $line = fgets($f); 5: while ($line !== FALSE) { 6: if (preg_match(/@/, $line)) 7: echo $line; 8: $line = fgets($f); 9: }10: fclose($f);11: copyright(c) 2012 kuwata-lab.com all rights reserved.
  17. 17. Before: ファイルを1行ずつ処理する 1: // タブ文字でフィールドに分解 2: $f = fopen($filename, r); 3: if ($f === FALSE) throw ....; 4: $line = fgets($f); 5: while ($line !== FALSE) { 6: $arr = explode("t", $line); 7: echo $arr[1], "n"; 8: $line = fgets($f); 9: } 汎用性の高いコードの中に10: fclose($f); 汎用性の低いコードが混在11: copyright(c) 2012 kuwata-lab.com all rights reserved.
  18. 18. After: ジェネレータ関数 汎用性の高い箇所を関数に抽出 1: function each_line($filename) { 2: $f = fopen($filename, r); 3: if ($f === FALSE) throw ....; 4: $line = fgets($f); 5: while ($line !== FALSE) { 6: $arr = explode("t", $line); yield $line; 7: echo $arr[1]; 汎用性の低い箇所を yield 文に 8: $line = fgets($f); 9: }10: fclose($f);11: } copyright(c) 2012 kuwata-lab.com all rights reserved.
  19. 19. After: メインプログラム 1: // ジェネレータオブジェクトを生成 2: $g = each_line($filename); 3: // メインループ 4: foreach ($g as $line) { 5: // 汎用性の低い処理 6: $arr = explode("t", $line); 7: echo $arr[1], "n"; 8: } copyright(c) 2012 kuwata-lab.com all rights reserved.
  20. 20. ジェネレータの利点◆ ループ処理から、汎用性の高い箇所だけを切り 出せる(再利用性の向上)◆◆◆ copyright(c) 2012 kuwata-lab.com all rights reserved.
  21. 21. もっとジェネレータ関数 ジェネレータオブジェクトを受け取り、新し い別のジェネレータオブジェクトを生成する 1: // ジェネレータオブジェクトを作成 受け取る function each_fields($g) { 2: $g = each_line($filename); 3: // ループ 配列やイテレータでも可 4: foreach ($g as $line) { 5: $arr = explode("t", $line); 6: echo $arr[1]; yield $arr; 7: } 8: } copyright(c) 2012 kuwata-lab.com all rights reserved.
  22. 22. ジェネレータを「重ねる」 1: // ジェネレータオブジェクトを生成 2: $g = each_line($filename); 3: $g = each_fields($g); ジェネレータから 4: // メインループ 別のジェネレータ 5: foreach ($g as $line) { を生成 $arr 6: $arr = explode("n", $line);                     7: echo $arr[1], "n"; 8: } copyright(c) 2012 kuwata-lab.com all rights reserved.
  23. 23. 1つの大きなループ vs. 複数の小さなループジェネレータ使用前 ジェネレータ使用後$f = fopen($filename, r); while ($line !== FALSE) {$line = fgets($f); yield $line;while ($line !== FALSE) { } $arr = explode("n",$line); echo $arr[1]; foreach ($g as $line) { $line = fgets($f); yield $arr;} }fclose($f); $g = each_line($filename); $g = each_fields($g); foreach ($g as $arr) { echo $arr[1], "n"; } copyright(c) 2012 kuwata-lab.com all rights reserved.
  24. 24. ジェネレータの利点◆ ループ処理から、汎用性の高い箇所だけを切り 出せる(再利用性の向上)◆ ひとつの大きなループを、複数の小さなループ に分解できる(ループの簡素化とPipeline化)◆◆ copyright(c) 2012 kuwata-lab.com all rights reserved.
  25. 25. 従来方法との比較:配列にすべて格納する 1: function each_line($filename) { 2: $f = fopen($filename, r); 3: $lines = array(); メモリを大量に消費 4: $line = fgets($f);(巨大データだと落ちる) 5: while ($line !== FALSE) { 6: $lines[] = $line; 7: $line = fgets($f); 8: } 9: fclose($f); すべてを読み込まないと10: return $lines; 結果が返ってこない11: } copyright(c) 2012 kuwata-lab.com all rights reserved.
  26. 26. 従来方法との比較:ジェネレータ 1: function each_line($filename) { 2: $f = fopen($filename, r); 3: 1度に1行しか読み込まない 4: $line = fgets($f); (巨大なデータでも落ちない) 5: while ($line !== FALSE) { 6: yield $line; 7: $line = fgets($f); 8: } 読み込んだはしから値を返す 9: fclose($f); (ストリーム処理に最適)1011: } copyright(c) 2012 kuwata-lab.com all rights reserved.
  27. 27. リダイレクト v.s. パイプラインすべてを配列に格納する ≒「リダイレクト」・巨大な中間ファイルが必要・最後まで処理しないと何も出力されないbash% command1 < input > tmp1bash% command2 < tmp1 > tmp2bash% command3 < tmp2 copyright(c) 2012 kuwata-lab.com all rights reserved.
  28. 28. リダイレクト v.s. パイプラインジェネレータを連結する ≒ 「パイプ」・巨大な中間ファイルがいらない・読み込んだはしから出力されるbash% cat input | command1 | command2 | command3 copyright(c) 2012 kuwata-lab.com all rights reserved.
  29. 29. ジェネレータの利点◆ ループ処理から、汎用性の高い箇所だけを切り 出せる(再利用性の向上)◆ ひとつの大きなループを、複数の小さなループ に分解できる(ループの簡素化とPipeline化)◆ メモリ消費量が少ない (巨大なデータを扱ってもプロセスが落ちない)◆ データを読んだはしから処理できる (ストリームデータも処理可能) copyright(c) 2012 kuwata-lab.com all rights reserved.
  30. 30. どんな使い道があるの?Advanced Generator copyright(c) 2012 kuwata-lab.com all rights reserved.
  31. 31. ジェネレータとインタラクション$g->send() … 次のyield文まで実行する メインプログラム ジェネレータ関数 からメインプログ ラムに値を返す foreach(){} yield $value $g->send($arg)メインプログラム ジェネレータ関数からジェネレータ関数に値を渡せる copyright(c) 2012 kuwata-lab.com all rights reserved.
  32. 32. ジェネレータとインタラクション双方向への値の受け渡しが可能にメインプログラム $value = $g->send("arg"); send()の引数が yield文の値に yield文の引数が send()の戻り値にジェネレータ関数 $arg = (yield "value"); copyright(c) 2012 kuwata-lab.com all rights reserved.
  33. 33. ジェネレータとインタラクションメインプログラム ジェネレータ関数1$g = gfunc(); function gfunc(){$ret = $g->send(1); $arg = yield; 2 3$ret = $g->send(2); while (条件式) { 4$ret = $g->send(3); $ret = ...; 5 $arg = (yield $ret); 6 var_dump($arg); } } copyright(c) 2012 kuwata-lab.com all rights reserved.
  34. 34. ジェネレータとインタラクションメインプログラム ジェネレータ関数$g = gfunc(); function gfunc(){$ret = $g->send(1); $arg = yield;$ret = $g->send(2); while (条件式) { 9 7$ret = $g->send(3); $ret = ...; 10 $arg = (yield $ret); 11 var_dump($arg); 8 } } copyright(c) 2012 kuwata-lab.com all rights reserved.
  35. 35. ジェネレータとインタラクションメインプログラム ジェネレータ関数$g = gfunc(); function gfunc(){$ret = $g->send(1); $arg = yield;$ret = $g->send(2); while (条件式) { 14$ret = $g->send(3); $ret = ...; 15 12 $arg = (yield $ret); 16 var_dump($arg); 13 } } copyright(c) 2012 kuwata-lab.com all rights reserved.
  36. 36. サンプル:数字あてゲーム 最初のsend()の引数値を変数に代入1: function guess_quiz($num) {2: $ans = yield;3: while ($num != $ans) {4: if ($ans > $num)5: $ans = (yield "too large");6: else7: $ans = (yield "too small");8: } 値を返し、かつsend()9: } の引数値を変数に代入 copyright(c) 2012 kuwata-lab.com all rights reserved.
  37. 37. サンプル:数字あてゲーム1: $g = guess_quiz(mt_rand(1, 100));2: do {3: echo "guess number (1-100): ";4: $ans = fgets(STDIN, 128);5: if ($ans === false) break;6: $hint = $g->send($ans);7: echo $hint ? $hint."n"8: : "Correct!n";9: } while ($hint); 値を送信し、かつ 次のyield文まで実行 copyright(c) 2012 kuwata-lab.com all rights reserved.
  38. 38. ジェネレータとマルチスレッド Process高機能 機能は限られるが Native Thread メモリ消費量が 極めて少ない (=大量生成可能) Green Thread Generator リソース消費量 : OSの機能として実現 : 言語やライブラリで実現 copyright(c) 2012 kuwata-lab.com all rights reserved.
  39. 39. ジェネレータと非同期処理ネストしたコールバック関数処理1(function($data) { 処理2(function($data) { 処理3(function($data) { .... }); 読みにくい、 }); 書きにくい、}); わかりにくい copyright(c) 2012 kuwata-lab.com all rights reserved.
  40. 40. ジェネレータと非同期処理コールバック関数を数珠つなぎ$d = new Deferred();$d->next(function($data) { ..処理1..;})->next(function($data) { ..処理2..;})->next(function($data) { ..処理3..; 記述量が多い、 書き方が不自然}); copyright(c) 2012 kuwata-lab.com all rights reserved.
  41. 41. ジェネレータと非同期処理ジェネレータfunction doSomething() { $data = yield; ...処理1...; $data = yield; ...処理2...; $data = yield; ...処理3...; 自然な書き方で わかりやすい!} copyright(c) 2012 kuwata-lab.com all rights reserved.
  42. 42. ジェネレータとページ遷移1: $response = フォームを表示();2: $request = (yield $response);3: $response = プレビューを表示($request);4: $request = (yield $response);5: データベースに登録($request); 複数ページにまたがる遷移を 非同期処理と同じように記述 ※ (詳しくは「継続ベース フレームワーク」でggr)※ Apache だとリクエストごとにすべてをリセットするので、実現できない。 PHP のビルトイン Web サーバのようなパーシステントプロセスのサーバを使って、 リクエストを超えてジェネレータオブジェクトを保持できるような仕組みが必要。 copyright(c) 2012 kuwata-lab.com all rights reserved.
  43. 43. 落とし穴:breakされた場合 1: function each_line($filename) { 2: $f = fopen($filename, r); 3: $line = fgets($f); 4: while ($line !== FALSE) { 5: yield $line; 6: $line = fgets($f); 7: } 8: fclose($f); 9: } 呼び出し側でbreakされると 終了処理が行われない!※ (しかもPHPにはfinallyがないorz)※ SPLFileObject も同じ問題を抱えているが、デストラクタで fclose() している。 copyright(c) 2012 kuwata-lab.com all rights reserved.
  44. 44. 落とし穴:リファクタリング奇数番目をyieldしてから、偶数番目をyieldする 1: function stepping($arr) { 2: $n = count($arr); 3: for ($i=0; $i<$n; $i+=2) 4: yield $arr[$i]; 5: for ($i=1; $i<$n; $i+=2) 6: yield $arr[$i]; 7: } DRYじゃない!関数化しよう! copyright(c) 2012 kuwata-lab.com all rights reserved.
  45. 45. 落とし穴:リファクタリングDRYになった、けど動かない! 1: function _sub($arr, $i, $n) { 2: for (; $i<$n; $i+=2) 3: yield $arr[$i]; 4: } 5: function stepping($arr) { 6: $n = count($arr); 7: _sub($arr, 0, $n); 8: _sub($arr, 1, $n); 9: } ジェネレータ関数を呼び出して いるがforeach文を使ってない copyright(c) 2012 kuwata-lab.com all rights reserved.
  46. 46. 落とし穴:リファクタリング動くようになった…けどなんか腑に落ちない 1: function _sub($arr, $i, $n) { 2: for (; $i<$n; $i+=2) 3: yield $arr[$i]; 4: } 5: function stepping($arr) { 6: $n = count($arr); 7: foreach (_sub($arr, 0, $n) as $x) 8: yield $x; 9: foreach (_sub($arr, 1, $n) as $x)10: yield $x;11: } copyright(c) 2012 kuwata-lab.com all rights reserved.
  47. 47. まとめ◆ $->send()を使うと、双方向での値の受け渡し が可能◆ マルチスレッドよりも低機能だが軽量◆ 非同期処理が自然な形で記述できる◆ 落とし穴もあるよ! copyright(c) 2012 kuwata-lab.com all rights reserved.
  48. 48. any questions?
  49. 49. おまけMore Things copyright(c) 2012 kuwata-lab.com all rights reserved.
  50. 50. おまけ:PHP5.5 コンパイル方法$ git clone https://github.com/nikic/php-src.git$ cd php-src/$ git checkout -b addGeneratorSupport origin/addGeneratorSupport$ ./buildconf$ apxs2=/usr/local/apache2/bin/apxs$ ./configure --with-apxs2=$apxs2$ nice -20 time make$ sapi/cli/php myexample.php copyright(c) 2012 kuwata-lab.com all rights reserved.
  51. 51. おまけ:インデックスつきyield 1: function gfunc() { // 実行結果 2: yield a; 0 => a 3: yield b; 1 => b 4: yield 99=>c; 99 => c 5: } 6: 7: $g = gfunc(); 8: foreach ($g as $k=>$v) { 9: echo "$k => $v n";10: } copyright(c) 2012 kuwata-lab.com all rights reserved.
  52. 52. おまけ:参照渡しでのyield 1: function &gfunc(&$arr) { // 実行結果 2: foreach ($arr as &$x){ array(3) { 3: yield $x; [0]=> 4: } int(11) 5: } [1]=> 6: int(21) 7: $arr = [10, 20, 30]; [2]=> 8: $g = gfunc($arr); &int(31) 9: foreach ($g as &$x) }10: $x += 1;11: var_dump($arr); copyright(c) 2012 kuwata-lab.com all rights reserved.
  53. 53. おまけ:クロージャとの比較それ、クロージャでもできるよ!function fib() { // 使い方 list($x, $y) = $closure = fib(); [0, 1]; $x = $closure(); return function() while ($x < 100) { use ($x, $y) { echo $x, "n"; $tmp = $x; $x = $closure(); list($x, $y) = } [$y, $x+$y]; return $tmp; };} copyright(c) 2012 kuwata-lab.com all rights reserved.
  54. 54. おまけ:クロージャとの比較クロージャ版 ジェネレータ版function fib() { function fib() { list($x, $y) = list($x, $y) = [0, 1]; [0, 1]; return function() while (TRUE) { use ($x, $y) { yield $x; $tmp = $x; list($x, $y) = list($x, $y) = [$y, $x+$y]; [$y, $x+$y]; } return $tmp; } }; ・毎回先頭から実行される ・前回の終了場所から自動} 制約 的に再開 (より自然な記述) ・すべてをreturnの前に書 ・yieldの後ろにも処理が書 かなければならない制約 ける (より自然な記述) copyright(c) 2012 kuwata-lab.com all rights reserved.
  55. 55. おまけ:内部イテレータとの比較それ、内部イテレータでもできるよ!function fib($fn) { // 使い方 list($x, $y) = fib(function($x) { [0, 1]; echo $x, "n"; while ($x < 100) { }); $fn($x); list($x, $y) = [$y, $x+$y]; }} copyright(c) 2012 kuwata-lab.com all rights reserved.
  56. 56. おまけ:内部イテレータとの比較ループの終了条件も指定したい場合は、非常にブサイクfunction fib($c,$fn){ // 使い方 終了条件と… list($x, $y) = fib( [0, 1]; function($x) { while ($c($x)) { return $x < 100; $fn($x); }, list($x, $y) = function($x) { [$y, $x+$y]; echo $x, "n"; } }} ); ボディ部の両方が必要 copyright(c) 2012 kuwata-lab.com all rights reserved.
  57. 57. おまけ:内部イテレータとの比較可変箇所が複数ならジェネレータのほうがよっぽどきれいfunction fib(){ // 使い方 list($x, $y) = foreach(fib() as $x){ [0, 1]; // 終了条件 while (TRUE) { if ($x >= 100) yield $x; break; list($x, $y) = // ボディ部 [$y, $x+$y]; echo $x, "n"; } };} copyright(c) 2012 kuwata-lab.com all rights reserved.
  58. 58. おまけ:内部イテレータとの比較Rubyでは、1つの無名関数 (ブロック) で「終了条件」と「ボディ部」の両方を指定できる。def fib() // 使い方 x, y = 0, 1 fib {|x| while true // 終了条件 yield x break if x >= 100 x, y = y, x+y // ボディ部 end puts xend } copyright(c) 2012 kuwata-lab.com all rights reserved.
  59. 59. おまけ:「継続 (Continuation)」との比較◆ 継続のほうができることが広い、 ジェネレータはそのサブセット ※◆ 継続はcall stackを丸ごとコピーする ので重い、 ジェネレータはstack flame1つだけなので軽い◆ 継続は理解するのがすーーーっごく難しい、 ジェネレータはわかりやすいし使いやすい※処理系により実装方法は異なる場合がある copyright(c) 2012 kuwata-lab.com all rights reserved.
  60. 60. おまけ:ベンチマーク Code: https://gist.github.com/3710544 配列に詰め込む Loop のは高コスト Array Generator Inner Iterator Closure 0 0.2 0.4 0.6 0.8 (sec)PHP: 5.5-addGeneratorSupportOS: MacOSXCPU: Core2DUO 2GHz copyright(c) 2012 kuwata-lab.com all rights reserved.
  61. 61. おまけ:ベンチマーク Code: https://gist.github.com/3710569 Loop ジェネレータは 十分に低コスト GeneratorG + explode()G + explode() + array() 0 0.5 1 1.5 2 (sec) ループ内処理 (explode()やarray()) のほうがよっぽど高コストPHP: 5.5-addGeneratorSupportOS: MacOSXCPU: Core2DUO 2GHz copyright(c) 2012 kuwata-lab.com all rights reserved.
  62. 62. おまけ:参考文献◆ What PHP 5.5 might look like http://nikic.github.com/2012/07/10/What-PHP-5-5-might-look-like.html◆ Request for Comments: Generators https://wiki.php.net/rfc/generators◆ Scheme/継続の種類と利用例 http://ja.wikibooks.org/wiki/Scheme/継続の種類と利用例◆ Vallog - 継続の実装方針 http://valvallow.blogspot.jp/2011/01/blog-post_11.html◆ Twisted Intro: 「コールバック」ではない方法 http://skitazaki.appspot.com/translation/twisted-intro-ja/p17.html◆ 境界を越える: 継続とWeb開発、そしてJavaプログラミング http://www.ibm.com/developerworks/jp/java/library/j-cb03216/index.html copyright(c) 2012 kuwata-lab.com all rights reserved.
  63. 63. おしまい

×