SlideShare a Scribd company logo
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.
本発表について
【目的】 • PHP5.5の新機能かもしれない「ジェネレータ」を、
       「なんだか凄そうだ」と思ってもらう。


【内容】 • ジェネレータって何?
      • どううれしいの?
      • どんなことに使えるの?


【注意】 • 内容は2012-09-15時点での情報に基づく。
       今後、仕様変更があり得るので注意。


          copyright(c) 2012 kuwata-lab.com all rights reserved.
ジェネレータって何?
What is Generator?




                copyright(c) 2012 kuwata-lab.com all rights reserved.
まとめ

◆ ジェネレータ                                     セーブ機能


◆ ジェネレータ関数                                   ゲームシナリオ


◆ ジェネレータオブジェクト                                            冒険の書
                                                        (セーブデータ)

◆ yield文                                     宿屋(セーブポイント)


           copyright(c) 2012 kuwata-lab.com all rights reserved.
通常の関数

	 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.
ジェネレータ (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.
使い方
         ジェネレータオブジェクトを生成
       (通常の関数と使い方が違うことに注意!)

	 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.
実行順序:ループ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.
実行順序:ループ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.
実行順序:ループ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.
サンプル: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.
サンプル:フィボナッチ数列 (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.
まとめ

◆ ジェネレータ                                     セーブ機能


◆ ジェネレータ関数                                   ゲームシナリオ


◆ ジェネレータオブジェクト                                            冒険の書
                                                        (セーブデータ)

◆ yield文                                     宿屋(セーブポイント)


           copyright(c) 2012 kuwata-lab.com all rights reserved.
どううれしいの?
Why Generator is so useful?




                copyright(c) 2012 kuwata-lab.com all rights reserved.
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.
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.
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.
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.
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.
ジェネレータの利点

◆ ループ処理から、汎用性の高い箇所だけを切り
  出せる(再利用性の向上)
◆


◆


◆



      copyright(c) 2012 kuwata-lab.com all rights reserved.
もっとジェネレータ関数
          ジェネレータオブジェクトを受け取り、新し
          い別のジェネレータオブジェクトを生成する

	 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.
ジェネレータを「重ねる」

	 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.
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.
ジェネレータの利点

◆ ループ処理から、汎用性の高い箇所だけを切り
  出せる(再利用性の向上)
◆ ひとつの大きなループを、複数の小さなループ
  に分解できる(ループの簡素化とPipeline化)
◆


◆



        copyright(c) 2012 kuwata-lab.com all rights reserved.
従来方法との比較:配列にすべて格納する

	 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.
従来方法との比較:ジェネレータ

	 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.
リダイレクト v.s. パイプライン

すべてを配列に格納する                                  ≒「リダイレクト」
・巨大な中間ファイルが必要
・最後まで処理しないと何も出力されない


bash% command1 < input > tmp1
bash% command2 < tmp1 > tmp2
bash% command3 < tmp2



          copyright(c) 2012 kuwata-lab.com all rights reserved.
リダイレクト v.s. パイプライン

ジェネレータを連結する                                  ≒ 「パイプ」
・巨大な中間ファイルがいらない
・読み込んだはしから出力される


bash% cat input | command1 
                | command2 
                | command3



          copyright(c) 2012 kuwata-lab.com all rights reserved.
ジェネレータの利点

◆ ループ処理から、汎用性の高い箇所だけを切り
  出せる(再利用性の向上)
◆ ひとつの大きなループを、複数の小さなループ
  に分解できる(ループの簡素化とPipeline化)
◆ メモリ消費量が少ない
  (巨大なデータを扱ってもプロセスが落ちない)
◆ データを読んだはしから処理できる
  (ストリームデータも処理可能)

        copyright(c) 2012 kuwata-lab.com all rights reserved.
どんな使い道があるの?
Advanced Generator




               copyright(c) 2012 kuwata-lab.com all rights reserved.
ジェネレータとインタラクション

$g->send() … 次のyield文まで実行する

             メインプログラム                                               ジェネレータ関数
                                                                    からメインプログ
                                                                    ラムに値を返す
   foreach(){}                                               yield	 $value
 $g->send($arg)

メインプログラム
                 ジェネレータ関数
からジェネレータ
関数に値を渡せる



            copyright(c) 2012 kuwata-lab.com all rights reserved.
ジェネレータとインタラクション

双方向への値の受け渡しが可能に
メインプログラム

    $value	 =	 $g->send("arg");

                                                       send()の引数が
                                                       yield文の値に

                                                      yield文の引数が
                                                      send()の戻り値に
ジェネレータ関数
     $arg	 =	 (yield	 "value");

           copyright(c) 2012 kuwata-lab.com all rights reserved.
ジェネレータとインタラクション

メインプログラム                                     ジェネレータ関数

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.
ジェネレータとインタラクション

メインプログラム                                   ジェネレータ関数
$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.
ジェネレータとインタラクション

メインプログラム                                   ジェネレータ関数
$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.
サンプル:数字あてゲーム
                            最初の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.
サンプル:数字あてゲーム

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.
ジェネレータとマルチスレッド

                                                                    Process
高機能



      機能は限られるが                                        Native Thread
      メモリ消費量が
      極めて少ない
      (=大量生成可能)
                                                 Green Thread

         Generator

                                                              リソース消費量
      : OSの機能として実現
      : 言語やライブラリで実現

            copyright(c) 2012 kuwata-lab.com all rights reserved.
ジェネレータと非同期処理

ネストしたコールバック関数
処理1(function($data)	 {
	 	 	 処理2(function($data)	 {
	 	 	 	 	 	 処理3(function($data)	 {
	 	 	 	 	 	 	 	 	 ....
	 	 	 	 	 	 });                                 読みにくい、
	 	 	 });                                       書きにくい、
});                                             わかりにくい




                    copyright(c) 2012 kuwata-lab.com all rights reserved.
ジェネレータと非同期処理

コールバック関数を数珠つなぎ
$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.
ジェネレータと非同期処理

ジェネレータ
function	 doSomething()	 {
	 	 	 $data	 =	 yield;
	 	 	 ...処理1...;
	 	 	 $data	 =	 yield;
	 	 	 ...処理2...;
	 	 	 $data	 =	 yield;
	 	 	 ...処理3...;          自然な書き方で
                          わかりやすい!
}


           copyright(c) 2012 kuwata-lab.com all rights reserved.
ジェネレータとページ遷移

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.
落とし穴: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.
落とし穴:リファクタリング

奇数番目を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.
落とし穴:リファクタリング

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.
落とし穴:リファクタリング

動くようになった…けどなんか腑に落ちない
	 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.
まとめ

◆ $->send()を使うと、双方向での値の受け渡し
  が可能
◆ マルチスレッドよりも低機能だが軽量
◆ 非同期処理が自然な形で記述できる
◆ 落とし穴もあるよ!




        copyright(c) 2012 kuwata-lab.com all rights reserved.
any questions?
おまけ
More Things




              copyright(c) 2012 kuwata-lab.com all rights reserved.
おまけ: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.
おまけ:インデックスつき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.
おまけ:参照渡しでの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.
おまけ:クロージャとの比較

それ、クロージャでもできるよ!
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.
おまけ:クロージャとの比較

クロージャ版                                       ジェネレータ版
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.
おまけ:内部イテレータとの比較

それ、内部イテレータでもできるよ!
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.
おまけ:内部イテレータとの比較

ループの終了条件も指定したい場合は、非常にブサイク
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.
おまけ:内部イテレータとの比較

可変箇所が複数ならジェネレータのほうがよっぽどきれい
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.
おまけ:内部イテレータとの比較
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	 x
end                                               }




                   copyright(c) 2012 kuwata-lab.com all rights reserved.
おまけ:「継続 (Continuation)」との比較

◆ 継続のほうができることが広い、
  ジェネレータはそのサブセット
                        ※
◆ 継続はcall stackを丸ごとコピーする ので重い、
  ジェネレータはstack flame1つだけなので軽い
◆ 継続は理解するのがすーーーっごく難しい、
  ジェネレータはわかりやすいし使いやすい




※処理系により実装方法は異なる場合がある

              copyright(c) 2012 kuwata-lab.com all rights reserved.
おまけ:ベンチマーク
                                                        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.
おまけ:ベンチマーク
                                                        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.
おまけ:参考文献

◆ 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.
おしまい

More Related Content

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

  • 1. PHPカンファレンス2012 PHP5.5新機能 かもしれない Generator 初心者入門 makoto kuwata <[email protected]> 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.
  • 3. ジェネレータって何? What is Generator? 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.
  • 14. どううれしいの? Why Generator is so useful? 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.
  • 20. ジェネレータの利点 ◆ ループ処理から、汎用性の高い箇所だけを切り 出せる(再利用性の向上) ◆ ◆ ◆ 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.
  • 24. ジェネレータの利点 ◆ ループ処理から、汎用性の高い箇所だけを切り 出せる(再利用性の向上) ◆ ひとつの大きなループを、複数の小さなループ に分解できる(ループの簡素化とPipeline化) ◆ ◆ 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.
  • 30. どんな使い道があるの? Advanced Generator copyright(c) 2012 kuwata-lab.com all rights reserved.
  • 31. ジェネレータとインタラクション $g->send() … 次のyield文まで実行する メインプログラム ジェネレータ関数 からメインプログ ラムに値を返す foreach(){} yield $value $g->send($arg) メインプログラム ジェネレータ関数 からジェネレータ 関数に値を渡せる 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.
  • 39. ジェネレータと非同期処理 ネストしたコールバック関数 処理1(function($data) { 処理2(function($data) { 処理3(function($data) { .... }); 読みにくい、 }); 書きにくい、 }); わかりにくい 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.
  • 41. ジェネレータと非同期処理 ジェネレータ function doSomething() { $data = yield; ...処理1...; $data = yield; ...処理2...; $data = yield; ...処理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.
  • 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. 落とし穴:リファクタリング 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.
  • 47. まとめ ◆ $->send()を使うと、双方向での値の受け渡し が可能 ◆ マルチスレッドよりも低機能だが軽量 ◆ 非同期処理が自然な形で記述できる ◆ 落とし穴もあるよ! copyright(c) 2012 kuwata-lab.com all rights reserved.
  • 49. おまけ More Things 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.
  • 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. おまけ:内部イテレータとの比較 ループの終了条件も指定したい場合は、非常にブサイク 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. おまけ:内部イテレータとの比較 可変箇所が複数ならジェネレータのほうがよっぽどきれい 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. おまけ:内部イテレータとの比較 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 x end } 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.