PHP

PHP7調査(24)ジェネレータがyield fromとreturnをサポート

More than 3 years have passed since last update.

ジェネレータはPHP5.5で導入された機能です。PHP7では、ジェネレータをさらに便利にするような文法としてyield fromreturnが実装されました。これはPython 3.3から文法ごと輸入したものです(たぶん)。それぞれ簡単に説明します。


yield from

PHP5では、ジェネレータ関数が値を返す構文はyieldしかありませんでした。PHP7では新たな構文が導入されます。

yield from <expr>

この文では<expr>から順に値を取り出し、それぞれの値をyieldします。<expr>にはTraversableなオブジェクトまたは配列を指定することができます。

<?php

function gen2($i) {
yield $i;
yield $i*2;
}

function gen1() {
yield from [1,2];
yield from gen2(4);
}

foreach (gen1() as $i) {
var_dump($i);
}

上記プログラムを実行すると下記のような結果が得られます。

int(1)

int(2)
int(4)
int(8)

これは「ジェネレータ関数の処理の一部を別のジェネレータに委譲する」などと説明されることも多いようです。巨大で複雑なジェネレータ関数を作るような場合に、この構文で簡単に関数分割ができますね。


ジェネレータ関数でのreturn

PHP5までのジェネレータ関数にはreturnを書くことはできませんでした。PHP7からは、ジェネレータ関数にreturnを書くことができるようになりました。returnのタイミングでジェネレータ関数は終了します。また、returnした値はジェネレータの新たなメソッドgetReturn()で取得することができます。

<?php

function gen() {
yield 1;
yield 2;
return 3;
}

$gen = gen();
foreach ($gen as $i) {
var_dump($i);
}
echo $gen->getReturn(), "\n";

上記プログラムを実行すると下記のような結果が得られます。

int(1)

int(2)
3

この例だとreturnの存在理由がわからない程度ですが、yield from文と組み合わせると少し違った動きになります。yield from文でジェネレータを呼び出した場合、ジェネレータのreturn値がyield from文の評価値になります。

<?php

function main() {
$gen = gen1(0);
$x = $gen->current();
printf("%d <--yield\n", $x);
$y = $gen->send(2);
printf("%d <--yield\n", $y);
$z = $gen->send(5);
printf("%d <--yield\n", $z);
$w = $gen->send(7);
printf("%d <--yield\n", $w);
$gen->send(10);
printf("%d <--return\n", $gen->getReturn());
}

function gen1($a) {
printf("arg--> %d\n", $a);
$b = yield 1;
printf("send--> %d\n", $b);
$c = yield from gen2(3);
printf("%d <--return\n", $c);
$d = yield 9;
printf("send--> %d\n", $d);
return 11;
}

function gen2($a) {
printf("arg--> %d\n", $a);
$b = yield 4;
printf("send--> %d\n", $b);
$c = yield 6;
printf("send--> %d\n", $c);
return 8;
}

main();

上記プログラムを実行すると下記のような結果が得られます。

arg--> 0

1 <--yield
send--> 2
arg--> 3
4 <--yield
send--> 5
6 <--yield
send--> 7
8 <--return
9 <--yield
send--> 10
11 <--return

yield fromで呼び出されているジェネレータ関数gen2()の返す値のうち、4と6はmain()が受け取り、8はgen1()が受け取っていることがわかります。このように、異なる2つの関数と値のやりとりができているのは特徴的だと言えます。

この例はかなり複雑なので、手元で試した方が理解が進むかもしれません。


感想など

将来的にはPythonのasyncioのようにyield fromを活用するライブラリが出てくると思いますが、それまでは謎の機能という扱いになりそうですね…。


参照