Upgrade to Pro — share decks privately, control downloads, hide ads and more …

yield再入門 #phpcon

yield再入門 #phpcon

Avatar for hideki kinjyo

hideki kinjyo PRO

July 19, 2026

More Decks by hideki kinjyo

Other Decks in Programming

Transcript

  1. 2 スライド番号この辺 本資料の文字サイズ・オブジェクトのサンプルです。 最大表示領域とあわせて、ご確認ください。 [見えにくい場合] 資料を公開済みなので、ぜひお手元にご用意ください => https://speakerdeck.com/o0h/phpcon-2026 or Fortee

    から ・太字もあります Shape ・カラー1・カラー2・カラー3 ・font1・font2・ font3 四 角 ) The sample code is written in this font. 本文や吹き出し内で この文字サイズ(48pt)の テキストが 使われています。 問題なく読めますか? 縦 下幅 まは で最 見 大 切で れこ ずこ にま 見で え使 まい すま かす ? ( 発表に入る前に: 環境テスト
  2. 23 自己紹介 • 金城秀樹 / きんじょうひでき • GitHub: @o0h /

    : @o0h_ • 好きなFWはCakePHP • アイコンは美味しい鮭親子丼の写真です • Podcast 「readline.fm」 • mix2コミュニティ 𝕏 「深夜に美味しいご飯の写真を上げよう部」管理人
  3. 27 yieldとGenerator function nanika2() { yield 100; } var_dump(nanika2()); object(Generator)#1

    (1) { ["function"]=> string(7) "nanika2" } yieldを含むfunctionは、 実行すると Generatorを生成する
  4. 28 強引なイメージによる説明(※正しくない) function nanikaX() { return new Generator(▪▪▪); } $g

    = nanikaX(); 「オブジェクトを生成する」 という側面だけで言うなら、 まずは、 こんな風に捉えると良いかも
  5. 31 Iteratorの実装例 $iterator = new class($data) implements Iterator { private

    array $keys; private int $position = 0; private array $data = ['a' => true, 'b' => false, 'c' => true]; public function __construct() { $this->keys = array_keys($this->data); } public function rewind(): void { $this->position = 0; } public function valid(): bool { return isset($this->keys[$this->position]); } public function current(): mixed { return $this->data[$this->keys[$this->position]]; } public function key(): mixed { return $this->keys[$this->position]; } public function next(): void { $this->position++; } }; Iterator実装を用意する オーバーヘッドや煩わしさ
  6. 32 Iteratorの実装例 $generator = (function() { yield 'a' => true;

    yield 'b' => false; yield 'c' => true; })(); yieldなら、これだけでOK。 「順番に値を返す」機能を 提供できる
  7. 36 yieldの最も基本的な形 function generator() { yield 'a' => true; yield

    'b' => false; yield 'c' => true; } $g = generator(); foreach ($g as $k => $v) { printf("%s is %s!!\n", $k, var_export($v, true)); } 単純なイテレーションの例
  8. 38 yieldの最も基本的な形 function generator() { yield 'a' => true; yield

    'b' => false; yield 'c' => true; } `yield` のあとに `キー => 式` $g = generator(); foreach ($g as $k => $v) { printf("%s is %s!!\n", $k, var_export($v, true)); }
  9. 39 yieldの最も基本的な形 function generator() { yield true; yield false; yield

    true; } `キー`は省略OK (代わりに0からの連番になる) $g = generator(); foreach ($g as $k => $v) { printf("%s is %s!!\n", $k, var_export($v, true)); }
  10. 40 yieldの最も基本的な形 function generator() { yield 'a' => true; yield

    'b' => false; yield 'c' => true; } $g = generator(); `yield` ごとに 1つずつ要素を取り出せる foreach ($g as $k => $v) { printf("%s is %s!!\n", $k, var_export($v, true)); }
  11. 41 yieldの最も基本的な形 function generator() { yield 'a' => true; yield

    'b' => false; yield 'c' => true; } $g = generator(); var_dump($g->a); var_dump($g['a']); Generatorには 添字でアクセス的なことは できない
  12. 49 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); この処理を 詳細に分解して見てみる
  13. 50 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); メインルーチンの1ステップ目 $a = $g->current(); $g->next(); $b = $g->current();
  14. 51 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); ジェネレータに 「現在の要素を返せ」と命じる
  15. 52 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); ジェネレータの内部処理へ
  16. 53 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); まだ何も処理をしていないので、 「現在位置」といっても 「どこにもいない」状態
  17. 54 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); 最初の「返せる位置」まで進める yield

    date(); yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current();
  18. 55 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); 最初の「返せる位置」まで進める yield

    date(); yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); `yield`は Generator内部の処理を 停止させる この停止機能こそが、 単なる`new Generator`の 糖衣構文ではない理由 (の1つ)
  19. 56 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); 停止するべき位置 = yieldが見つかったので
  20. 57 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); 右側にある式を評価して
  21. 58 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); 呼び出し元に値を返す yield

    date(); yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current();
  22. 59 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); 返された値の代入
  23. 60 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); 次の位置まで処理を進める $g->next(); $b = $g->current();
  24. 61 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); 内部位置は今ココ
  25. 62 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); 次の位置がココ
  26. 63 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); この間を進める
  27. 64 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); この間を進める `yield`のもう1つの重要機能 「再開」 内部的な「現在位置」から 続きだけを処理する
  28. 65 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); 右側の式を評価して、 yield

    date(); 処理を停止 yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current();
  29. 66 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current();
  30. 67 Generatorの処理をstep-by-stepで function generator() { yield random_int(1, 10); 既に「停止済み」なので、 最初の`current()`

    と違い yield date(); 位置の走査をせず、評価済みの結果を返す yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current();
  31. 69 イテレーションの遅延処理 function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); 再掲: Generatorは 「途中で止める」 「途中から動かす」 が出来るので・・
  32. 70 イテレーションの遅延処理 function generator() { yield random_int(1, 10); yield date();

    yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); これを活かして、 遅延処理が可能になる
  33. 71 イテレーションの遅延処理 function a($start) { $data = []; for ($i

    = $start; $i <= PHP_INT_MAX; $i++) { $data[] = $i; } return $data; } function b($start) { for ($i = $start; $i <= PHP_INT_MAX; $i++) { yield $i; } }
  34. 72 イテレーションの遅延処理 function a($start) { $data = []; for ($i

    = $start; $i <= PHP_INT_MAX; $i++) { $data[] = $i; } 予めデータセットを全部作ろうとすると return $data; メモリ溢れを起こすが・・ } function b($start) { for ($i = $start; $i <= PHP_INT_MAX; $i++) { yield $i; } }
  35. 73 イテレーションの遅延処理 function a($start) { $data = []; for ($i

    = $start; $i <= PHP_INT_MAX; $i++) { $data[] = $i; } ジェネレータで1つずつ取り出すと、 return $data; 軽い処理で済む } function b($start) { for ($i = $start; $i <= PHP_INT_MAX; $i++) { yield $i; } }
  36. 74 イテレーションの遅延処理 function filesInDir($directory, $fileExtension) { $directory = realpath($directory); $it

    = new RecursiveDirectoryIterator($directory); $it = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::LEAVES_ONLY); $it = new RegexIterator($it, '(\.'.preg_quote($fileExtension).'$)'); foreach ($it as $file) { $fileName = $file->getPathname(); yield $fileName => file_get_contents($fileName); } } <https://github.com/nikic/PHP-Parser/blob/v5.8.0/test/bootstrap.php>
  37. 75 イテレーションの遅延処理 function filesInDir($directory, $fileExtension) { $directory = realpath($directory); $it

    = new RecursiveDirectoryIterator($directory); $it = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::LEAVES_ONLY); $it = new RegexIterator($it, '(\.'.preg_quote($fileExtension).'$)'); 「値を返せ」と言われた時に、 foreach ($it as $file)重い(メモリを食う)処理をしている例 { $fileName = $file->getPathname(); yield $fileName => file_get_contents($fileName); } } <https://github.com/nikic/PHP-Parser/blob/v5.8.0/test/bootstrap.php>
  38. 80 yield/Generator関連のRFC 3選 1. PHP: rfc:generators https://wiki.php.net/rfc/generators 2. PHP: rfc:generator-return-expressions

    https://wiki.php.net/rfc/generator-return-expressions 3. PHP: rfc:generator-delegation https://wiki.php.net/rfc/generator-delegation
  39. 81 yield/Generator関連のRFC 3選 1. PHP: rfc:generators https://wiki.php.net/rfc/generators はじまり。 5.5で実装済み 2.

    PHP: rfc:generator-return-expressions https://wiki.php.net/rfc/generator-return-expressions 3. PHP: rfc:generator-delegation https://wiki.php.net/rfc/generator-delegation
  40. 82 yield/Generator関連のRFC 3選 1. PHP: rfc:generators https://wiki.php.net/rfc/generators getReturn() 7.0で実装済み 2.

    PHP: rfc:generator-return-expressions https://wiki.php.net/rfc/generator-return-expressions 3. PHP: rfc:generator-delegation https://wiki.php.net/rfc/generator-delegation
  41. 83 yield/Generator関連のRFC 3選 1. PHP: rfc:generators https://wiki.php.net/rfc/generators 2. PHP: rfc:generator-return-expressions

    yield from。 https://wiki.php.net/rfc/generator-return-expressions 7.0で実装済み 3. PHP: rfc:generator-delegation https://wiki.php.net/rfc/generator-delegation
  42. 97 大前提となる知識 PHPのプログラム(スクリプトは)、 「スクリプト」 ⇒ 「AST」⇒「オペコード」⇒「機械語」 と翻訳されて、最終的に実行される <?php class A

    { ————— ————— ソースコード <?php T_OPEN_TAG if T_IF \s T_WHITESPACE $a T_VARIABLE \s T_WHITESPACE echo T_ECHO $x T_VARIABLE トークン S E Op E var AST val 0 1 2 3 4 5 INIT_FCALL 'hoge' SEND_VAL 1 SEND_VAL 10 DO_ICALL $0 ECHO $0 RETURN 1 オペコード 00110000 00100000 00100000 01001001 01001110 01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 機械語
  43. 98 ASTを見てみる コードを 木構造化する <?php class A { ————— —————

    ソースコード <?php T_OPEN_TAG if T_IF \s T_WHITESPACE $a T_VARIABLE \s T_WHITESPACE echo T_ECHO $x T_VARIABLE トークン S E Op E var AST val 0 1 2 3 4 5 INIT_FCALL 'hoge' SEND_VAL 1 SEND_VAL 10 DO_ICALL $0 ECHO $0 RETURN 1 オペコード 00110000 00100000 00100000 01001001 01001110 01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 機械語
  44. 99 ASTノード: 普通の関数 function X() { return random_int(1, 100); }

    ZEND_AST_FUNC_DECL(name=X) ├ params: ZEND_AST_PARAM_LIST └ body: ZEND_AST_STMT_LIST └ stmt[0]: ZEND_AST_RETURN └ expr: ZEND_AST_CALL ├ callable: ZEND_AST_ZVAL("random_int") └ args: ZEND_AST_ARG_LIST ├ arg[0]: ZEND_AST_ZVAL(1) └ arg[1]: ZEND_AST_ZVAL(100)
  45. 100 ASTノード: ジェネレータ関数 function X() { yield random_int(1, 100); }

    ZEND_AST_FUNC_DECL(name=X) ├ params: ZEND_AST_PARAM_LIST └ body: ZEND_AST_STMT_LIST └ stmt[0]: ZEND_AST_YIELD └ expr: ZEND_AST_CALL ├ callable: ZEND_AST_ZVAL("random_int") └ args: ZEND_AST_ARG_LIST ├ arg[0]: ZEND_AST_ZVAL(1) └ arg[1]: ZEND_AST_ZVAL(100)
  46. 101 オペコードを見てみる 木をコンパイルして 低次元なコードにする <?php class A { ————— —————

    ソースコード <?php T_OPEN_TAG if T_IF \s T_WHITESPACE $a T_VARIABLE \s T_WHITESPACE echo T_ECHO $x T_VARIABLE トークン S E Op E var AST val 0 1 2 3 4 5 INIT_FCALL 'hoge' SEND_VAL 1 SEND_VAL 10 DO_ICALL $0 ECHO $0 RETURN 1 オペコード 00110000 00100000 00100000 01001001 01001110 01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 機械語
  47. 102 ASTのトラバース&コンパイル コンパイル: ZEND_AST_FUNC_DECL(name=X) 木を走査していって、 ├ params: ZEND_AST_PARAM_LIST 「対応するコードとデータ) └

    body: ZEND_AST_STMT_LIST に変換していく └ stmt[0]: ZEND_AST_RETURN └ expr: ZEND_AST_CALL ├ callable: ZEND_AST_ZVAL("random_int") └ args: ZEND_AST_ARG_LIST ├ arg[0]: ZEND_AST_ZVAL(1) └ arg[1]: ZEND_AST_ZVAL(100)
  48. 103 ASTのトラバース&コンパイル ZEND_AST_FUNC_DECL(name=X) ├ params: ZEND_AST_PARAM_LIST └ body: ZEND_AST_STMT_LIST 例えば、

    └ stmt[0]: ZEND_AST_RETURN `ZEND_AST_FUNC_DECL`なら └関数宣言ノード expr: ZEND_AST_CALL ├ `zend_compile_func_decl()` callable: ZEND_AST_ZVAL("random_int") が担当 └ args: ZEND_AST_ARG_LIST ├ arg[0]: ZEND_AST_ZVAL(1) └ arg[1]: ZEND_AST_ZVAL(100)
  49. 104 ASTのトラバース&コンパイル ZEND_AST_FUNC_DECL(name=X) ├ params: ZEND_AST_PARAM_LIST └ body: ZEND_AST_STMT_LIST └

    stmt[0]: ZEND_AST_RETURN └ expr: ZEND_AST_CALL ├ callable: ZEND_AST_ZVAL("random_int") returnに対応する `ZEND_AST_RETURN`なら └ args: ZEND_AST_ARG_LIST が担当 ├ arg[0]:`zend_compile_return()` ZEND_AST_ZVAL(1) └ arg[1]: ZEND_AST_ZVAL(100)
  50. 105 オペコード 0000 INIT_FCALL 2 112 string("random_int") 0001 SEND_VAL int(1)

    1 0002 SEND_VAL int(100) 2 0003 V0 = DO_ICALL 通常の関数に対して 0004 RETURN V0 生成されたオペコード 0005 RETURN null
  51. 106 オペコード 0000 GENERATOR_CREATE 0001 INIT_FCALL 2 112 string("random_int") 0002

    SEND_VAL int(1) 1 0003 SEND_VAL int(100) 2 0004 V0 = DO_ICALL ジェネレータ関数の 0005 YIELD V0 オペコード 0006 GENERATOR_RETURN null
  52. 107 オペコード 0000 GENERATOR_CREATE 0001 INIT_FCALL 2 112 string("random_int") 0002

    SEND_VAL int(1) 1 明らかに通常の関数とは 0003 SEND_VAL int(100) 2 別のコードが現れた 0004 V0 = DO_ICALL 0005 YIELD V0 0006 GENERATOR_RETURN null
  53. 109 どこで関数/ジェネレータ関数を区別しているか 実は、重要な仕掛けは 「ASTの走査」よりも手前のフェーズにある ここ大事 <?php class A { —————

    ————— ソースコード <?php T_OPEN_TAG if T_IF \s T_WHITESPACE $a T_VARIABLE \s T_WHITESPACE echo T_ECHO $x T_VARIABLE トークン S E Op E var AST val 0 1 2 3 4 5 INIT_FCALL 'hoge' SEND_VAL 1 SEND_VAL 10 DO_ICALL $0 ECHO $0 RETURN 1 オペコード 00110000 00100000 00100000 01001001 01001110 01001001 01010100 01011111 01000110 01000011 01000001 01001100 01001100 00100000 00100111 01101000 01101111 01100111 01100101 00100111 00001010 00110001 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00001010 00110010 00100000 00100000 01010011 01000101 01001110 01000100 01011111 01010110 01000001 01001100 00100000 00100000 00100000 00110001 00110000 00001010 00110011 00100000 機械語
  54. 110 パース処理(zend_language_parser.y) T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL);

    CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_language_parser.y#L1382-L1385>
  55. 111 パース処理(zend_language_parser.y) `yield <expression>` が来たら T_YIELD expr { $$ =

    zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_language_parser.y#L1382-L1385>
  56. 112 パース処理(zend_language_parser.y) 何かゴニョる T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2,

    NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_language_parser.y#L1382-L1385>
  57. 113 パース処理(zend_language_parser.y) ノード`ZEND_AST_YIELD`を生成して 「戻り値」にセットする T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD,

    $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_language_parser.y#L1382-L1385>
  58. 114 パース処理(zend_language_parser.y) (こっちが注目したいポイント) コンパイラのグローバル管理の値に T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD,

    $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_language_parser.y#L1382-L1385>
  59. 115 パース処理(zend_language_parser.y) (こっちが注目したいポイント) ジェネレータを示すフラグをセット T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD,

    $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_language_parser.y#L1382-L1385>
  60. 116 コンパイルの中身 if ( CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR ) { zend_mark_function_as_generator();

    zend_emit_op( NULL, ZEND_GENERATOR_CREATE, NULL, `zend_compile_func_decl()` から NULL 到達してくる処理の中で ); } <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_language_parser.y#L1382-L1385>
  61. 117 コンパイルの中身 if ( CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR ) { zend_mark_function_as_generator();

    `ZEND_ACC_GENERATOR` フラグが zend_emit_op( 立っていたら NULL, ZEND_GENERATOR_CREATE, NULL, NULL ); } <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_language_parser.y#L1382-L1385>
  62. 118 コンパイルの中身 if ( CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR ) { zend_mark_function_as_generator();

    zend_emit_op( NULL, ジェネレータ関数を示す ZEND_GENERATOR_CREATE, オペコードを出力する NULL, NULL ); } <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_language_parser.y#L1382-L1385>
  63. 119 オペコード 0000 GENERATOR_CREATE 0001 INIT_FCALL 2 112 string("random_int") 0002

    SEND_VAL int(1) 1 0003 SEND_VAL int(100) 2 再掲: 0004 V0 = DO_ICALL できあがりの風景 0005 YIELD V0 0006 GENERATOR_RETURN null
  64. 122 こんな事を話しました • yield, yield from, current(), next(), send() •

    ジェネレータ、イテレータ、停止、再開 • PHPのジェネレータは、生まれた時から送受信 • 関数っぽいけどフラグを立てて独自処理が入るジェネレータ関数 普通のオブジェクトっぽいけど少し拡張されるGenerator構造体
  65. 128 Webにある記事etc • RFCでも参照されていた、ジェネレータ・コルーチンに関するプ レゼンテーション(Python畑) • https://www.dabeaz.com/generators/ • https://www.dabeaz.com/coroutines/ •

    実装者(nikicさん)による、PHPのジェネレータでの協調的マルチ タスクの実装コンセンプトの紹介記事 • https://www.npopov.com/2012/12/22/Cooperative-multitaskingusing-coroutines-in-PHP.html
  66. 129 PHP5.5 / 7.0リリース当時の記事など • PHP5.5新機能「ジェネレータ」初心者入門 https://www.slideshare.net/slideshow/php55/14301315 • PHP7調査(24)ジェネレータがyield fromとreturnをサポート

    #PHP - Qiita https://qiita.com/hnw/items/fb9bff53987978b11dde • PHP5.5のジェネレータをSPLのイテレータと組み合わせてみる hnwの日記 https://hnw.hatenablog.com/entry/20130114
  67. 134 yieldとGenerator function nanika2() { yield 100; } var_dump(nanika2()); object(Generator)#1

    (1) { ["function"]=> string(7) "nanika2" } おさらい: yieldを使うと Generatorができる
  68. 139 Generatorクラスとは何者か、どこから来るのか • 通常の `function` は、 `return` を0個以上含むが • そこに、(AND|OR)

    `yield` を含む場合には • 呼び出し時に、値を返す代わりにGeneratorオブジェクトを返す 最初の不思議ポイントかも
  69. 141 強引なイメージによる説明(※正しくない) function nanikaX() { return new Generator(▪▪▪); } $g

    = nanikaX(); 「オブジェクトを生成する」 という側面だけで言うなら、 まずは、 こんな風に捉えると良いかも
  70. 151 反復の処理をしよう for ($i=0; $i < 10; $i++) { $item

    = $data[$i]; // nanika shori... } 例えば、コレも反復処理
  71. 152 反復の処理をしよう for ($i=0; $i < 10; $i++) { $item

    = $data[$i]; // nanika shori... } これで上手くいくには 「データの長さは10である」 「キーが0〜9の連番である」 ことを、 事前に知っておかないとダメ
  72. 153 反復の処理をしよう $data = [ 'a' => true, 'b' =>

    false, 'c' => true, ]; for($i=0; $i < 10; $i++){ $item = $data[$i]; // nanika shori... } こういうのには通用しない
  73. 154 反復の処理をしよう $keys = array_keys($data); $len = count($keys); for($i=0; $i<$len;

    $i++) { $key = $keys[$i]; $item = $data[$k]; // nanika shori... } これで大丈夫 (面倒くさいですね)
  74. 156 イテレータパターン while ($data->hasNext()) { $item = $data->getNext(); // nanika

    shori... } これで大丈夫 (簡単になりましたね!)
  75. 160 IteratorインターフェイスのAPI(契約) interface Iterator extends Traversable { public function current():

    mixed public function key(): mixed public function next(): void public function rewind(): void public function valid(): bool } 6つのメソッドを用意しないといけない
  76. 161 Iteratorの実装例 $iterator = new class($data) implements Iterator { private

    array $keys; private int $position = 0; private array $data = ['a' => true, 'b' => false, 'c' => true]; public function __construct() { $this->keys = array_keys($this->data); } public function rewind(): void { $this->position = 0; } public function valid(): bool { return isset($this->keys[$this->position]); } public function current(): mixed { return $this->data[$this->keys[$this->position]]; } public function key(): mixed { return $this->keys[$this->position]; } public function next(): void { $this->position++; } }; Iterator実装を用意する オーバーヘッドや煩わしさ
  77. 162 Iteratorの実装例 $generator = (function() { yield 'a' => true;

    yield 'b' => false; yield 'c' => true; })(); yieldなら、これだけで 「順番に値を返す」機能が 提供される
  78. 167 yieldの「受信」機能 function generator() { yield 'a'; yield 'b'; yield

    'c'; } `yield` は 「処理を止めて」 「値を返す」、 もしくは「再開地点」 という話をしましたが・・
  79. 168 yieldの「受信」機能 function generator() { $x = yield 'a'; var_dump("received:

    x={$x}"); $y = yield 'b'; var_dump("received: x={$x}, y={$y}"); } $g = generator(); echo "current: [{$g->current()}]\n"; $g->send('AAA'); echo "current: [{$g->current()}]\n"; $g->send('BBB'); echo "current: [{$g->current()}"]\n";
  80. 169 yieldの「受信」機能 function generator() { $x = yield 'a'; var_dump("received:

    x={$x}"); $y = yield 'b'; var_dump("received: x={$x}, y={$y}"); } `send($value)` = 値を送って、次の停止位置まで進める: $g = generator(); echo "current: [{$g->current()}]\n"; $g->send('AAA'); コレまで見てきたものは 「右にある式の結果」を使ったが、 echo "current: [{$g->current()}]\n"; 「外から値を渡して、左側に渡す」動きになる $g->send('BBB'); echo "current: [{$g->current()}"]\n";
  81. 170 yieldの「受信」機能 function generator() { (初期状態なので、最初の) $x = yield 'a';

    停止位置まで進ませ、 呼び出し元に `a`を返す var_dump("received: x={$x}"); $y = yield 'b'; var_dump("received: x={$x}, y={$y}"); } $g = generator(); echo "current: [{$g->current()}]\n"; $g->send('AAA'); echo "current: [{$g->current()}]\n"; $g->send('BBB'); echo "current: [{$g->current()}"]\n";
  82. 171 yieldの「受信」機能 function generator() { 現在位置の`yield`に $x = yield 'a';

    引数`AAA`を入れて、 var_dump("received: x={$x}"); 左に返す $y = yield 'b'; var_dump("received: x={$x}, y={$y}"); } $g = generator(); echo "current: [{$g->current()}]\n"; $g->send('AAA'); echo "current: [{$g->current()}]\n"; $g->send('BBB'); echo "current: [{$g->current()}"]\n"; つまり、 `$x = 'AAA';` として 処理が進む
  83. 172 yieldの「受信」機能 function generator() { $x = yield 'a'; var_dump("received:

    x={$x}"); $y = yield 'b'; `next()`と同じ様に var_dump("received: x={$x}, y={$y}"); 次の停止位置まで進める } $g = generator(); echo "current: [{$g->current()}]\n"; $g->send('AAA'); echo "current: [{$g->current()}]\n"; $g->send('BBB'); echo "current: [{$g->current()}"]\n";
  84. 173 yieldの「受信」機能 string(15) "received: x=AAA" function generator() { $x =

    yield 'a'; var_dump("received: x={$x}"); $y = yield 'b'; var_dump("received: x={$x}, y={$y}"); } $g = generator(); echo "current: [{$g->current()}]\n"; $g->send('AAA'); echo "current: [{$g->current()}]\n"; $g->send('BBB'); echo "current: [{$g->current()}"]\n";
  85. 174 yieldの「受信」機能 function generator() { $x = yield 'a'; var_dump("received:

    x={$x}"); ここで停止 $y = yield 'b'; var_dump("received: x={$x}, y={$y}"); } $g = generator(); echo "current: [{$g->current()}]\n"; $g->send('AAA'); echo "current: [{$g->current()}]\n"; $g->send('BBB'); echo "current: [{$g->current()}"]\n";
  86. 175 yieldの「受信」機能 function generator() { $x = yield 'a'; var_dump("received:

    x={$x}"); $y = yield 'b'; var_dump("received: x={$x}, y={$y}"); } 現在の値を返す => echo (current: [b]) $g = generator(); echo "current: [{$g->current()}]\n"; $g->send('AAA'); echo "current: [{$g->current()}]\n"; $g->send('BBB'); echo "current: [{$g->current()}"]\n";
  87. 176 yieldの「受信」機能 function generator() { $x = yield 'a'; var_dump("received:

    x={$x}"); $y = yield 'b'; var_dump("received: x={$x}, y={$y}"); } $g = generator(); echo "current: [{$g->current()}]\n"; `BBB`を渡して、次の停止位置まで進める $g->send('AAA'); echo "current: [{$g->current()}]\n"; よって、var_dumpまで処理。 $g->send('BBB'); => string(22) "received: x=AAA, y=BBB" echo "current: [{$g->current()}"]\n";
  88. 177 yieldに例外も送り込める function generator() { try { yield; } catch

    (\LogicException $e) { `throw ($exception)` var_dump($e->getMessage()); =停止位置から、任意の例外を投げさせる: } } つまり、yieldの部分を throw 文に置き換えるようなイメージになる $g = generator(); $g->throw( new \LogicException('dou desu ka?') );
  89. 178 yieldに例外も送り込める function generator() { try { yield; } catch

    (\LogicException $e) { var_dump($e->getMessage()); } } string(12) "dou desu ka?" $g = generator(); $g->throw( new \LogicException('dou desu ka?') );
  90. 180 generator関数のreturn対応 function generator() { foreach([1, 100, 1000] as $v)

    { yield $v; } return 'done!'; } `getReturn()` =戻り値を取得する: $g = generator(); foreach ($g as $v) { /** nanka suru */} 関数内の `return` の値を取得する var_dump($g->current()); var_dump($g->getReturn())
  91. 181 generator関数のreturn対応 function generator() { foreach([1, 100, 1000] as $v)

    { yield $v; } return 'done!'; } $g = generator(); foreach ($g as $v) { /** nanka suru */} NULL string(5) "done!" var_dump($g->current()); var_dump($g->getReturn())
  92. 182 generator関数のreturn対応 function generator() { yield 1; yield 2; return

    'done!'; } $g = generator(); $g->next(); var_dump($g->getReturn()); まだ`return` に 到達していない状態だと・・
  93. 183 generator関数のreturn対応 function generator() { yield 1; yield 2; return

    'done!'; メソッド呼び出し時に例外を投げる => Fatal error: Uncaught Exception: Cannot get return } value of a generator that hasn't returned in XXX $g = generator(); $g->next(); var_dump($g->getReturn());
  94. 185 ジェネレータの「委譲」 function generator() { yield from [1, 2, 3];

    yield 100; } $g = generator(); foreach ($g as $v) { var_dump($v); } `yield from` = 別のiterableから値をyieldする: 処理を委譲する・ジェネレータを合成する、 という感じに
  95. 186 ジェネレータの「委譲」 function generator() { yield from [1, 2, 3];

    yield 100; } $g = generator(); foreach ($g as $v) { var_dump($v); } int(1) int(2) int(3) int(100)
  96. 192 ※補足: yieldの「受信」ってこんなやつです function generator() { $x = yield 'a';

    var_dump("received: x={$x}"); $y = yield 'b'; var_dump("received: x={$x}, y={$y}"); } $g = generator(); echo "current: [{$g->current()}]\n"; $g->send('AAA'); echo "current: [{$g->current()}]\n"; $g->send('BBB'); echo "current: [{$g->current()}"]\n";
  97. 193 ※補足: yieldの「受信」ってこんなやつです function generator() { $x = yield 'a';

    var_dump("received: x={$x}"); $y = yield 'b'; var_dump("received: x={$x}, y={$y}"); } `send($value)` = 値を送って、次の停止位置まで進める: $g = generator(); echo "current: [{$g->current()}]\n"; $g->send('AAA'); コレまで見てきたものは 「右にある式の結果」を使ったが、 echo "current: [{$g->current()}]\n"; 「外から値を渡して、左側に渡す」動きになる $g->send('BBB'); echo "current: [{$g->current()}"]\n";
  98. 194 ※補足: yieldの「受信」ってこんなやつです string(15) "received: x=AAA" function generator() { $x

    = yield 'a'; var_dump("received: x={$x}"); $y = yield 'b'; var_dump("received: x={$x}, y={$y}"); } $g = generator(); echo "current: [{$g->current()}]\n"; $g->send('AAA'); echo "current: [{$g->current()}]\n"; $g->send('BBB'); echo "current: [{$g->current()}"]\n";
  99. なぜ `send()` `throw()`が入ったのか・・? • 恐らく、Pythonのジェネレータ/yieldの影響が大きい • Pythonでは、 1. 最初に「値を返すだけ」のジェネレータが実装されて 2.

    その後に、「値を受け取って再開」の機能が実装されている • 時系列的に、PHP側は↑の「2」まで進んだ後のRFCであり 設計としても進化の道筋としても、 参考になっているのではないか 196
  100. 202 Generator Return Expressions • (委譲によって) ジェネレータ内から、別のジェネレータを呼べるようになった • 逆に言えば、 「子ジェネレータが終わったのを、どう処理するか」問題

    `yield from` で受け取った値は、「委譲元」ではなく「委譲元の呼び 出し元」に渡ってしまう(パススルー) • 値を引き継げないし、終了判定も難しい、すると「協調」しにくい・・ • • `return` を使えるようにすることで、これらの問題を解決
  101. 208 Generatorの構造体 struct _zend_generator { zend_object std; zend_execute_data *execute_data; zend_execute_data

    *frozen_call_stack; zval value; zval key; これが zval retval; 「`send()`の受け口」 zval *send_target; の保持場所 <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_generators.h#L57C8-L96>
  102. 210 *send_targetの利用(セットアップ) if (RETURN_VALUE_USED(opline)) { generator->send_target = EX_VAR(opline->result.var); ZVAL_NULL(generator->send_target); yield式の

    } else { generator->send_target = NULL; 返り値が使われていたら } つまり、 `$x = yield`みたいな <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_vm_def.h#L8544-L8551>
  103. 211 *send_targetの利用(セットアップ) if (RETURN_VALUE_USED(opline)) { generator->send_target = EX_VAR(opline->result.var); ZVAL_NULL(generator->send_target); }

    else { yield式の代入先(左辺)のポインタを generator->send_target = NULL; } send_targetにセット つまり、 `$x = yield`の `$x` <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_vm_def.h#L8544-L8551>
  104. 212 Generator::send()の処理 `send()` ZEND_METHOD(Generator, send) { (中略) メソッドのコード root =

    zend_generator_get_current(generator); if (root->send_target && !(root->flags & ZEND_GENERATOR_CURRENTLY_RUNNING)) { ZVAL_COPY(root->send_target, value); } zend_generator_resume(generator); <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_generators.c#L998-L1030>
  105. 213 Generator::send()の処理 ZEND_METHOD(Generator, send) { (中略) `send_target` が設定されていたら root =

    zend_generator_get_current(generator); if (root->send_target && !(root->flags & ZEND_GENERATOR_CURRENTLY_RUNNING)) { ZVAL_COPY(root->send_target, value); } zend_generator_resume(generator); <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_generators.c#L998-L1030>
  106. 214 Generator::send()の処理 ZEND_METHOD(Generator, send) { (中略) root = zend_generator_get_current(generator); if

    (root->send_target && 送られてきたvalueをセットして !(root->flags & ZEND_GENERATOR_CURRENTLY_RUNNING)) { ZVAL_COPY(root->send_target, value); } zend_generator_resume(generator); <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_generators.c#L998-L1030>
  107. 215 Generator::send()の処理 ZEND_METHOD(Generator, send) { (中略) root = zend_generator_get_current(generator); if

    (root->send_target && !(root->flags & ZEND_GENERATOR_CURRENTLY_RUNNING)) { ZVAL_COPY(root->send_target, value); 処理を再開(resume)する } zend_generator_resume(generator); <https://github.com/php/php-src/blob/fe52e5b6/Zend/zend_generators.c#L998-L1030>