More Related Content
Similar to PHP5.5新機能「ジェネレータ」初心者入門 (20)
PHP5.5新機能「ジェネレータ」初心者入門
- 1. PHPカンファレンス2012
PHP5.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. 本発表について
【目的】 • PHP5.5の新機能かもしれない「ジェネレータ」を、
「なんだか凄そうだ」と思ってもらう。
【内容】 • ジェネレータって何?
• どううれしいの?
• どんなことに使えるの?
【注意】 • 内容は2012-09-15時点での情報に基づく。
今後、仕様変更があり得るので注意。
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 4. まとめ
◆ ジェネレータ セーブ機能
◆ ジェネレータ関数 ゲームシナリオ
◆ ジェネレータオブジェクト 冒険の書
(セーブデータ)
◆ yield文 宿屋(セーブポイント)
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 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. ジェネレータ (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. 使い方
ジェネレータオブジェクトを生成
(通常の関数と使い方が違うことに注意!)
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. 実行順序:ループ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. 実行順序:ループ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. 実行順序:ループ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. サンプル: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. サンプル:フィボナッチ数列 (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. まとめ
◆ ジェネレータ セーブ機能
◆ ジェネレータ関数 ゲームシナリオ
◆ ジェネレータオブジェクト 冒険の書
(セーブデータ)
◆ yield文 宿屋(セーブポイント)
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 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. 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. 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. 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. 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.
- 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. ジェネレータを「重ねる」
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. 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.
- 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. 従来方法との比較:ジェネレータ
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); (ストリーム処理に最適)
10
11: }
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 27. リダイレクト v.s. パイプライン
すべてを配列に格納する ≒「リダイレクト」
・巨大な中間ファイルが必要
・最後まで処理しないと何も出力されない
bash% command1 < input > tmp1
bash% command2 < tmp1 > tmp2
bash% command3 < tmp2
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 28. リダイレクト v.s. パイプライン
ジェネレータを連結する ≒ 「パイプ」
・巨大な中間ファイルがいらない
・読み込んだはしから出力される
bash% cat input | command1
| command2
| command3
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 29. ジェネレータの利点
◆ ループ処理から、汎用性の高い箇所だけを切り
出せる(再利用性の向上)
◆ ひとつの大きなループを、複数の小さなループ
に分解できる(ループの簡素化とPipeline化)
◆ メモリ消費量が少ない
(巨大なデータを扱ってもプロセスが落ちない)
◆ データを読んだはしから処理できる
(ストリームデータも処理可能)
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 32. ジェネレータとインタラクション
双方向への値の受け渡しが可能に
メインプログラム
$value = $g->send("arg");
send()の引数が
yield文の値に
yield文の引数が
send()の戻り値に
ジェネレータ関数
$arg = (yield "value");
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 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. ジェネレータとインタラクション
メインプログラム ジェネレータ関数
$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. ジェネレータとインタラクション
メインプログラム ジェネレータ関数
$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. サンプル:数字あてゲーム
最初のsend()の引数値を変数に代入
1: function guess_quiz($num) {
2: $ans = yield;
3: while ($num != $ans) {
4: if ($ans > $num)
5: $ans = (yield "too large");
6: else
7: $ans = (yield "too small");
8: } 値を返し、かつsend()
9: } の引数値を変数に代入
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 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. ジェネレータとマルチスレッド
Process
高機能
機能は限られるが Native Thread
メモリ消費量が
極めて少ない
(=大量生成可能)
Green Thread
Generator
リソース消費量
: OSの機能として実現
: 言語やライブラリで実現
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 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.
- 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. 落とし穴: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.
- 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. 落とし穴:リファクタリング
動くようになった…けどなんか腑に落ちない
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.
- 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. おまけ:インデックスつき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. おまけ:参照渡しでの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. おまけ:クロージャとの比較
それ、クロージャでもできるよ!
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. おまけ:クロージャとの比較
クロージャ版 ジェネレータ版
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.
- 59. おまけ:「継続 (Continuation)」との比較
◆ 継続のほうができることが広い、
ジェネレータはそのサブセット
※
◆ 継続はcall stackを丸ごとコピーする ので重い、
ジェネレータはstack flame1つだけなので軽い
◆ 継続は理解するのがすーーーっごく難しい、
ジェネレータはわかりやすいし使いやすい
※処理系により実装方法は異なる場合がある
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 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-addGeneratorSupport
OS: MacOSX
CPU: Core2DUO 2GHz
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 61. おまけ:ベンチマーク
Code: https://gist.github.com/3710569
Loop ジェネレータは
十分に低コスト
Generator
G + explode()
G + explode()
+ array()
0 0.5 1 1.5 2
(sec)
ループ内処理 (explode()やarray())
のほうがよっぽど高コスト
PHP: 5.5-addGeneratorSupport
OS: MacOSX
CPU: Core2DUO 2GHz
copyright(c) 2012 kuwata-lab.com all rights reserved.
- 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.