Slide 1

Slide 1 text

yield再入門 PHP Conference Japan 2026 Hideki Kinjyo GitHub: o0h / X: @o0h_ [公開用] v1.0.4

Slide 2

Slide 2 text

2 みなさん、どうも こんにちyield〜〜!🎉

Slide 3

Slide 3 text

3 PHPには `yield` というものが あります

Slide 4

Slide 4 text

4 since PHP 5.5〜 PHPには `yield` というものが ありまして

Slide 5

Slide 5 text

5 マニュアル曰く 最もシンプルな書きかたを すると、yield 文の見た目 は return 文とほぼ同じに なります。

Slide 6

Slide 6 text

function nanika() { return 100; }

Slide 7

Slide 7 text

function nanika() { return 100; } returnを置き換えるだけ function nanika2() { yield 100; }

Slide 8

Slide 8 text

function nanika() { return 100; } var_dump(nanika()); int(100)

Slide 9

Slide 9 text

function nanika2() { yield 100; } var_dump(nanika2()); object(Generator)#1 (1) { ["function"]=> string(7) "nanika2" }

Slide 10

Slide 10 text

function nanika2() { yield 100; } var_dump(nanika2()); ? Generator object(Generator)#1 (1) { ["function"]=> string(7) "nanika2" }

Slide 11

Slide 11 text

11 マニュアル(当時)を見てみても・・

Slide 12

Slide 12 text

12 「Generatorが追加された」「yield使うよ」 と紹介されている

Slide 13

Slide 13 text

13 yield = Generatorのための キーワード

Slide 14

Slide 14 text

14 ★☆ 本日のテーマ☆★ yieldやGeneratorと 顔見知りになろう

Slide 15

Slide 15 text

15 こんな感じで話します 1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装

Slide 16

Slide 16 text

16 こんな感じで話します 1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方 そもそも、コレ何だっけ?の話 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装

Slide 17

Slide 17 text

17 こんな感じで話します 1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装 yieldが持つ機能や 呼び出し方の共有

Slide 18

Slide 18 text

18 こんな感じで話します 1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装 この機能の、 そもそもの「意図」を探る

Slide 19

Slide 19 text

19 こんな感じで話します 1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装 「何をもたらしているのか」を 客観的な事実から理解

Slide 20

Slide 20 text

20 3つの観点から、 ただの「return」や 「イテレータ」との違いを 理解していきましょう

Slide 21

Slide 21 text

21 自己紹介 • 金城秀樹 / きんじょうひでき • GitHub: @o0h / : @o0h_ • 好きなFWはCakePHP • アイコンは美味しい鮭親子丼の写真です • Podcast 「readline.fm」 • mixi2コミュニティ 𝕏 「深夜に美味しいご飯の写真を上げよう部」管理人

Slide 22

Slide 22 text

22 ご連絡 この資料の後半に、 発表時間の都合でカットしたものを 付けてあります 良かったらご覧ください!!

Slide 23

Slide 23 text

1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装 #phpcon #track2

Slide 24

Slide 24 text

サマリーだけ話します 1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装 #phpcon #track2

Slide 25

Slide 25 text

25 yieldとGenerator function nanika2() { yield 100; } var_dump(nanika2()); object(Generator)#1 (1) { ["function"]=> string(7) "nanika2" } yieldを含むfunctionは、 実行すると Generatorを生成する

Slide 26

Slide 26 text

26 強引なイメージによる説明(※正しくない) function nanikaX() { return new Generator(■■■); } $g = nanikaX(); 「オブジェクトを生成する」 という側面だけで言うなら、 まずは、 こんな風に捉えると良いかも

Slide 27

Slide 27 text

27 シンプルなイテレータを簡単に作れるやつ!!

Slide 28

Slide 28 text

28 PHPと反復処理 iterable(疑似型) Traversable Iterator PHPにおける 反復処理の対応 array 「foreachに渡せる」もの と覚えればOK IteratorAggregate

Slide 29

Slide 29 text

29 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実装を用意する オーバーヘッドや煩わしさ

Slide 30

Slide 30 text

30 Iteratorの実装例 $generator = (function() { yield 'a' => true; yield 'b' => false; yield 'c' => true; })(); yieldなら、これだけでOK。 「順番に値を返す」機能を 提供できる

Slide 31

Slide 31 text

✔ DONE 1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装 #phpcon #track2

Slide 32

Slide 32 text

1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装 #phpcon #track2

Slide 33

Slide 33 text

Generatorとイテレーション

Slide 34

Slide 34 text

34 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)); } 単純なイテレーションの例

Slide 35

Slide 35 text

35 yieldの最も基本的な形 a is true!! b is false!! c is true!! 実行結果

Slide 36

Slide 36 text

36 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)); }

Slide 37

Slide 37 text

37 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)); }

Slide 38

Slide 38 text

38 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)); }

Slide 39

Slide 39 text

39 yieldの最も基本的な形 function generator() { yield 'a' => true; yield 'b' => false; yield 'c' => true; } $g = generator(); var_dump($g->a); var_dump($g['a']); Generatorには 添字でアクセス的なことは できない

Slide 40

Slide 40 text

40 Iterator APIの利用 $g = generator(); var_dump($g->current()); var_dump($g->key()); $g->next(); var_dump($g->current()); var_dump($g->key()); Iteratorインターフェイス のAPIを利用した例

Slide 41

Slide 41 text

41 Iterator APIの利用 bool(true) string(1) "a" bool(false) string(1) "b" 実行結果

Slide 42

Slide 42 text

42 Iterator APIの利用 $g = generator(); var_dump($g->current()); var_dump($g->key()); $g->next(); var_dump($g->current()); var_dump($g->key()); Iteratorインターフェイス のAPIを利用した例

Slide 43

Slide 43 text

43 Iterator APIの利用 現在の要素を返す $g = generator(); var_dump($g->current()); var_dump($g->key()); $g->next(); 現在の要素のキーを返す var_dump($g->current()); var_dump($g->key());

Slide 44

Slide 44 text

44 Iterator APIの利用 $g = generator(); var_dump($g->current()); var_dump($g->key()); $g->next(); 次の要素に進む var_dump($g->current()); var_dump($g->key());

Slide 45

Slide 45 text

Generatorと中断・再開

Slide 46

Slide 46 text

46 Iterator APIの利用・・・なのだけど $g->current(); $g->next(); (シレっと出てきたけど) コレらは何なのか?

Slide 47

Slide 47 text

47 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(); この処理を 詳細に分解して見てみる

Slide 48

Slide 48 text

48 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();

Slide 49

Slide 49 text

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(); ジェネレータに 「現在の要素を返せ」と命じる

Slide 50

Slide 50 text

50 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(); ジェネレータの内部処理へ

Slide 51

Slide 51 text

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(); まだ何も処理をしていないので、 「現在位置」といっても 「どこにもいない」状態

Slide 52

Slide 52 text

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();

Slide 53

Slide 53 text

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(); `yield`は Generator内部の処理を 停止させる この停止機能こそが、 単なる`new Generator`の 糖衣構文ではない理由 (の1つ)

Slide 54

Slide 54 text

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(); 停止するべき位置 = yieldが見つかったので

Slide 55

Slide 55 text

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(); 右側にある式を評価して

Slide 56

Slide 56 text

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();

Slide 57

Slide 57 text

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(); 返された値の代入

Slide 58

Slide 58 text

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();

Slide 59

Slide 59 text

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(); 内部位置は今ココ

Slide 60

Slide 60 text

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(); 次の位置がココ

Slide 61

Slide 61 text

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(); この間を進める

Slide 62

Slide 62 text

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(); この間を進める `yield`のもう1つの重要機能 「再開」 内部的な「現在位置」から 続きだけを処理する

Slide 63

Slide 63 text

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();

Slide 64

Slide 64 text

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();

Slide 65

Slide 65 text

65 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();

Slide 66

Slide 66 text

中断・再開と遅延処理

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

68 イテレーションの遅延処理 function generator() { yield random_int(1, 10); yield date(); yield false; } $g = generator(); $a = $g->current(); $g->next(); $b = $g->current(); これを活かして、 遅延処理が可能になる

Slide 69

Slide 69 text

69 イテレーションの遅延処理 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; } }

Slide 70

Slide 70 text

70 イテレーションの遅延処理 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; } }

Slide 71

Slide 71 text

71 イテレーションの遅延処理 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; } }

Slide 72

Slide 72 text

72 イテレーションの遅延処理 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); } }

Slide 73

Slide 73 text

73 イテレーションの遅延処理 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); } }

Slide 74

Slide 74 text

✔ DONE 1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装 #phpcon #track2

Slide 75

Slide 75 text

1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装 #phpcon #track2

Slide 76

Slide 76 text

76 PHPの`yield`って どんな気持ちで 使っていくのが 良いんだろう・・・

Slide 77

Slide 77 text

77 歴史に訊いてみる (実装背景を知りたい)

Slide 78

Slide 78 text

78 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

Slide 79

Slide 79 text

79 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

Slide 80

Slide 80 text

80 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

Slide 81

Slide 81 text

81 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

Slide 82

Slide 82 text

今回の発表で 取り上げるのはコチラ! PHP: rfc:generators

Slide 83

Slide 83 text

83

Slide 84

Slide 84 text

84 ジェネレータがあれば、 簡単かつオマジナイも無しで イテレータを実現できるよ!

Slide 85

Slide 85 text

85

Slide 86

Slide 86 text

86 めっちゃ具体の例として、 PHPUnitのDataProviderの話なんだけど・・

Slide 87

Slide 87 text

87 特定ディレクトリにあるテストファイルを 全部読み込んで、処理するような処理 でも、一気に全部メモリに載せるのは 結構しんどそうだ・・・

Slide 88

Slide 88 text

88 もちろん、真面目にIteratorを使えば 再帰処理も遅延読み込みもできる! ・・・でも! yieldで、もっと簡単に解決できる!!

Slide 89

Slide 89 text

1. yield/generator/iteratorのおさらい ✔ DONE 2. 文法や機能など、基本的な使い方 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装 #phpcon #track2

Slide 90

Slide 90 text

90 有益情報のコーナー • 「コルーチンって何、いきなり出てきたね・・」 と思った人に素晴らしい情報です • 『n月刊ラムダノート Vol.1, No.1(2019)』を 買ってください!! • 第2特集『「 コルーチン」とは何だったのか?』 とにかく素晴らしいので

Slide 91

Slide 91 text

1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装 #phpcon #track2

Slide 92

Slide 92 text

92 動き方や、 想いは分かってきたけど

Slide 93

Slide 93 text

93 それって一体、 どうやってるんですか? (を知りたいですよね)

Slide 94

Slide 94 text

どうやって「普通の関数」が 「ジェネレータ関数」になるのか

Slide 95

Slide 95 text

95 大前提となる知識 PHPのプログラム(スクリプトは)、 「スクリプト」 ⇒ 「AST」⇒「オペコード」⇒「機械語」 と翻訳されて、最終的に実行される

Slide 96

Slide 96 text

96 ASTを見てみる コードを 木構造化する

Slide 97

Slide 97 text

97 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)

Slide 98

Slide 98 text

98 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)

Slide 99

Slide 99 text

99 オペコードを見てみる 木をコンパイルして 低次元なコードにする

Slide 100

Slide 100 text

100 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)

Slide 101

Slide 101 text

101 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)

Slide 102

Slide 102 text

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") returnに対応する `ZEND_AST_RETURN`なら └ args: ZEND_AST_ARG_LIST が担当 ├ arg[0]:`zend_compile_return()` ZEND_AST_ZVAL(1) └ arg[1]: ZEND_AST_ZVAL(100)

Slide 103

Slide 103 text

103 オペコード 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

Slide 104

Slide 104 text

104 オペコード 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

Slide 105

Slide 105 text

105 オペコード 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

Slide 106

Slide 106 text

106 どこで関数/ジェネレータ関数を区別しているか `ZEND_AST_FUNC_DECL`ノードを見ただけでは、 (= そのサブノードを見ていない状態で) 自分が「どっちの関数か?」は判断できないはず・・・ なぜ、オペコードを出し分けられているのか???

Slide 107

Slide 107 text

107 どこで関数/ジェネレータ関数を区別しているか 実は、重要な仕掛けは 「ASTの走査」よりも手前のフェーズにある ここ大事

Slide 108

Slide 108 text

108 パース処理(zend_language_parser.y) T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }

Slide 109

Slide 109 text

109 パース処理(zend_language_parser.y) `yield ` が来たら T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }

Slide 110

Slide 110 text

110 パース処理(zend_language_parser.y) 何かゴニョる T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }

Slide 111

Slide 111 text

111 パース処理(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; }

Slide 112

Slide 112 text

112 パース処理(zend_language_parser.y) (こっちが注目したいポイント) コンパイラのグローバル管理の値に T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }

Slide 113

Slide 113 text

113 パース処理(zend_language_parser.y) (こっちが注目したいポイント) ジェネレータを示すフラグをセット T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }

Slide 114

Slide 114 text

114 コンパイルの中身 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 到達してくる処理の中で ); }

Slide 115

Slide 115 text

115 コンパイルの中身 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 ); }

Slide 116

Slide 116 text

116 コンパイルの中身 if ( CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR ) { zend_mark_function_as_generator(); zend_emit_op( NULL, ジェネレータ関数を示す ZEND_GENERATOR_CREATE, オペコードを出力する NULL, NULL ); }

Slide 117

Slide 117 text

117 オペコード 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

Slide 118

Slide 118 text

1. yield/generator/iteratorのおさらい 2. 文法や機能など、基本的な使い方✔ DONE 3. 導入された背景やねらいなど、歴史 4. PHPの内部で何をしているのか、実装 #phpcon #track2

Slide 119

Slide 119 text

おしまいに #phpcon #track2

Slide 120

Slide 120 text

120 こんな事を話しました • yield, yield from, current(), next(), send() • ジェネレータ、イテレータ、停止、再開 • PHPのジェネレータは、生まれた時から送受信 • 関数っぽいけどフラグを立てて独自処理が入るジェネレータ関数 普通のオブジェクトっぽいけど少し拡張されるGenerator構造体

Slide 121

Slide 121 text

121 ★☆ 本日のテーマ☆★ yieldやGeneratorと 顔見知りになろう

Slide 122

Slide 122 text

122 なれましたかね??

Slide 123

Slide 123 text

123 この時間を境に 「意外と使えるかも」 となったら幸い 濫用はNO!

Slide 124

Slide 124 text

おしまい! お付き合いいただき ありがとうございました!!

Slide 125

Slide 125 text

Appendix 参考リソース #phpcon #track2

Slide 126

Slide 126 text

126 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

Slide 127

Slide 127 text

127 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

Slide 128

Slide 128 text

128 発展的な内容 • PHPにおけるI/O多重化とyield https://www.slideshare.net/slideshow/phpioyieldphpcon2014/40136098 • PHP 5.5 yield and promises - Speaker Deck https://speakerdeck.com/kaja47/php-5-dot-5-yield-andpromises

Slide 129

Slide 129 text

129 利用方法よりの話 • Iteratorをimplementsする奴は情弱。IteratorAggregateを使え #PHP - Qiita https://qiita.com/Hiraku/items/14722922441f9ed3fbbb • 個人的にも、IteratorAggregateとの組み合わせは良さそうだなーと 思っています • PHP Generators - A useful example | Mark Story https://mark-story.com/posts/view/php-generators-a-usefulexample

Slide 130

Slide 130 text

130 PHPの内部処理について • 「AST」「オペコード」といった概念に触れましたが、 その辺りの話題に関する発表を過去にしています • ソースコード→AST→オペコード、の旅を覗いてみる - Speaker Deck https://speakerdeck.com/o0h/phpcon-kagawa-2026

Slide 131

Slide 131 text

Appendix-Ⅱ 「1章 yield/generator/iteratorのおさらい」の 補強・補足スライド #phpcon #track2

Slide 132

Slide 132 text

132 yieldとGenerator function nanika2() { yield 100; } var_dump(nanika2()); object(Generator)#1 (1) { ["function"]=> string(7) "nanika2" } おさらい: yieldを使うと Generatorができる

Slide 133

Slide 133 text

133 GeneratorクラスのAPI Generatorクラスが持っているAPI(メソッド)は、 Iteratorインターフェイスの機能の他に主に3つ 1. send($value) 2. getReturn() 3. throw($exception)

Slide 134

Slide 134 text

134 GeneratorクラスのAPI あなたは何者? どこから来たの??

Slide 135

Slide 135 text

Generator クラス

Slide 136

Slide 136 text

136 さっき見たスライド function nanika2() { yield 100; } var_dump(nanika2()); object(Generator)#1 (1) { ["function"]=> string(7) "nanika2" }

Slide 137

Slide 137 text

137 Generatorクラスとは何者か、どこから来るのか • 通常の `function` は、 `return` を0個以上含むが • そこに、(AND|OR) `yield` を含む場合には • 呼び出し時に、値を返す代わりにGeneratorオブジェクトを返す 最初の不思議ポイントかも

Slide 138

Slide 138 text

138 強引なイメージによる説明(※正しくない) function nanikaX() { yield ■■■; } $g = nanikaX(); このコードは・・・

Slide 139

Slide 139 text

139 強引なイメージによる説明(※正しくない) function nanikaX() { return new Generator(■■■); } $g = nanikaX(); 「オブジェクトを生成する」 という側面だけで言うなら、 まずは、 こんな風に捉えると良いかも

Slide 140

Slide 140 text

140 強引なイメージによる説明(※正しくない) `Generator`は 特殊なクラスであり、 `new`で作れない。 `yield`の利用が 唯一の方法となる

Slide 141

Slide 141 text

141 強引なイメージによる説明(※正しくない) • 他にも、 yieldは「単にオブジェクトを作るだけ」ではない点が 正しくないポイント • 後で触れていきます!

Slide 142

Slide 142 text

ジェネレータ

Slide 143

Slide 143 text

143 値を(次々と生成して)呼び出し元に渡すやつ!

Slide 144

Slide 144 text

144 シンプルなイテレータを簡単に作れるやつ!!

Slide 145

Slide 145 text

145 ? ? ?が2つ浮かんできます シンプルなイテレータを簡単に作れるやつ!!

Slide 146

Slide 146 text

イテレータ ─そして、それは簡単に作れないのか─

Slide 147

Slide 147 text

147 iterate【動詞】 • 繰り返す、反復する • repeat が「同じ事を繰り返す」のに対して、 「順番に進める」「逐次的に処理する」ようなニュアンス

Slide 148

Slide 148 text

148 イテレータ = 繰り返せるもの • 「イテレータパターン」、有名ですね • 集約オブジェクトに関して、 (利用者側が詳しく内部構造を知らなくても) 適切な順番で要素を取り出せるようにするパターン

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

150 反復の処理をしよう for ($i=0; $i < 10; $i++) { $item = $data[$i]; // nanika shori... } これで上手くいくには 「データの長さは10である」 「キーが0〜9の連番である」 ことを、 事前に知っておかないとダメ

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

152 反復の処理をしよう $keys = array_keys($data); $len = count($keys); for($i=0; $i<$len; $i++) { $key = $keys[$i]; $item = $data[$k]; // nanika shori... } これで大丈夫 (面倒くさいですね)

Slide 153

Slide 153 text

153 イテレータパターン • 利用者側が、内部構造(キーや要素数)を知らなくても良くする • (すごくザックリ言うと) 「まだありますか」「次をください」に応えられるようにする

Slide 154

Slide 154 text

154 イテレータパターン while ($data->hasNext()) { $item = $data->getNext(); // nanika shori... } これで大丈夫 (簡単になりましたね!)

Slide 155

Slide 155 text

155 PHPと反復処理 iterable(疑似型) Traversable Iterator PHPにおける 反復処理の対応 array 「foreachに渡せる」もの と覚えればOK IteratorAggregate

Slide 156

Slide 156 text

156 PHPと反復処理 iterable(疑似型) 言い換えると、 「オブジェクトをforeach Traversable array で使う」には 特定Interfaceの実装クラ スが必要 Iterator IteratorAggregate

Slide 157

Slide 157 text

157 思い出す

Slide 158

Slide 158 text

158 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つのメソッドを用意しないといけない

Slide 159

Slide 159 text

159 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実装を用意する オーバーヘッドや煩わしさ

Slide 160

Slide 160 text

160 Iteratorの実装例 $generator = (function() { yield 'a' => true; yield 'b' => false; yield 'c' => true; })(); yieldなら、これだけで 「順番に値を返す」機能が 提供される

Slide 161

Slide 161 text

161 ? ? 浮かんできた2つの「?」 これで解消できた シンプルなイテレータを簡単に作れるやつ!!

Slide 162

Slide 162 text

✔ DONE 1. yield/generator/iteratorのおさらい #phpcon #track2

Slide 163

Slide 163 text

Appendix-Ⅲ 「2章 文法や機能など、基本的な使い方」の 補強・補足スライド #phpcon #track2

Slide 164

Slide 164 text

Generator::send() Generator::throw()

Slide 165

Slide 165 text

165 yieldの「受信」機能 function generator() { yield 'a'; yield 'b'; yield 'c'; } `yield` は 「処理を止めて」 「値を返す」、 もしくは「再開地点」 という話をしましたが・・

Slide 166

Slide 166 text

166 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";

Slide 167

Slide 167 text

167 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";

Slide 168

Slide 168 text

168 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";

Slide 169

Slide 169 text

169 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';` として 処理が進む

Slide 170

Slide 170 text

170 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";

Slide 171

Slide 171 text

171 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";

Slide 172

Slide 172 text

172 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";

Slide 173

Slide 173 text

173 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";

Slide 174

Slide 174 text

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"; `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";

Slide 175

Slide 175 text

175 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?') );

Slide 176

Slide 176 text

176 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?') );

Slide 177

Slide 177 text

Generator::getReturn()

Slide 178

Slide 178 text

178 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())

Slide 179

Slide 179 text

179 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())

Slide 180

Slide 180 text

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

Slide 181

Slide 181 text

181 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());

Slide 182

Slide 182 text

yield from

Slide 183

Slide 183 text

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

Slide 184

Slide 184 text

184 ジェネレータの「委譲」 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)

Slide 185

Slide 185 text

✔ DONE 2. 文法や機能など、基本的な使い方 #phpcon #track2

Slide 186

Slide 186 text

Appendix-Ⅳ 「3章導入された背景やねらいなど、歴史」の 補強・補足スライド #phpcon #track2

Slide 187

Slide 187 text

発表では 割愛した部分! PHP: rfc:generators

Slide 188

Slide 188 text

188

Slide 189

Slide 189 text

189 「値を返す」だけではなくて 「受け取り」も可能になる(する) 「拡張された」、「反対方向の」ジェネーレータとか コルーチンとか呼ばれるような機能

Slide 190

Slide 190 text

190 ※補足: 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";

Slide 191

Slide 191 text

191 ※補足: 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";

Slide 192

Slide 192 text

192 ※補足: 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";

Slide 193

Slide 193 text

193 なぜ 「受信」が入ったのか・・? • 正直な所、「簡単に使えるイテレータを」に比べて 「sendが欲しい」動機をしっかりと読み取れなかった・・ • 技術的には、「next()を入れるなら、send()も近い実装になる」 • `send(null)` を、そのまま `next()` として使えば良いので

Slide 194

Slide 194 text

なぜ `send()` `throw()`が入ったのか・・? • 恐らく、Pythonのジェネレータ/yieldの影響が大きい • Pythonでは、 1. 最初に「値を返すだけ」のジェネレータが実装されて 2. その後に、「値を受け取って再開」の機能が実装されている • 時系列的に、PHP側は↑の「2」まで進んだ後のRFCであり 設計としても進化の道筋としても、 参考になっているのではないか 194

Slide 195

Slide 195 text

195 RFC上でも、 関連リソースとして 直接Pythonでの話が 言及されていたり

Slide 196

Slide 196 text

196 php-srcへのコミットにも Pythonへの言及があったり

Slide 197

Slide 197 text

PHP: rfc:generator-return-expressions PHP: rfc:generator-delegation

Slide 198

Slide 198 text

198 2つのRFC • `yield from` と、「ジェネレータ関数のreturn対応」は 同じモチベーションを元に、分割して提出されている • コルーチンとしての特徴を活かして、協調的マルチタスクを・・ というのが共通ゴール

Slide 199

Slide 199 text

199 Generator Delegation • 複雑な処理を行う関数を分割(リファクタ)するように、 ジェネレータも分割したい! • そこで、「委譲処理」を可能にしよう • 生まれたのが`yield from` 記法/機能

Slide 200

Slide 200 text

200 Generator Return Expressions • (委譲によって) ジェネレータ内から、別のジェネレータを呼べるようになった • 逆に言えば、 「子ジェネレータが終わったのを、どう処理するか」問題 `yield from` で受け取った値は、「委譲元」ではなく「委譲元の呼び 出し元」に渡ってしまう(パススルー) • 値を引き継げないし、終了判定も難しい、すると「協調」しにくい・・ • • `return` を使えるようにすることで、これらの問題を解決

Slide 201

Slide 201 text

✔ DONE 3. 導入された背景やねらいなど、歴史 #phpcon #track2

Slide 202

Slide 202 text

Appendix-Ⅴ 「4章 PHPの内部で何をしているのか、実装」の 補強・補足スライド #phpcon #track2

Slide 203

Slide 203 text

どうやって「値の受け取り」をしているのか

Slide 204

Slide 204 text

204 Generatorが持つ特別な値(フィールド) (通常のクラスのインスタンスのような)オブジェクトに加えて、 Generatorの特殊能力を実現するために持っているものがある • 現在の停止位置(オペコード上の何行目か) • `key()` `current()` で返すための値 • etc

Slide 205

Slide 205 text

205 Generatorが持つ特別な値(フィールド) その1つに、 「`send($v)` された時に、その値をどこに格納するか」 という情報も持っている

Slide 206

Slide 206 text

206 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; の保持場所

Slide 207

Slide 207 text

207 *send_targetの利用(セットアップ) yield式の処理の一部 if (RETURN_VALUE_USED(opline)) { generator->send_target = EX_VAR(opline->result.var); ZVAL_NULL(generator->send_target); } else { generator->send_target = NULL; }

Slide 208

Slide 208 text

208 *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`みたいな

Slide 209

Slide 209 text

209 *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`

Slide 210

Slide 210 text

210 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);

Slide 211

Slide 211 text

211 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);

Slide 212

Slide 212 text

212 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);

Slide 213

Slide 213 text

213 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);

Slide 214

Slide 214 text

✔ DONE 5. PHPの内部で何をしているのか、実装 #phpcon #track2