ジェネレータ関数の見た目はふつうの関数とほぼ同じです。違うのは、値を返すのではなく、 必要なだけ値を yield することです。
ジェネレータ関数が呼ばれると、反復処理が可能なオブジェクトを返します。 このオブジェクトを (foreach ループなどで) 反復させると、 値が必要になるたびに PHP がジェネレータ関数を呼びます。 そして、ジェネレータが値を yield した時点の状態を保存しておき、 次に値が必要になったときにはそこから再開できるようにします。
yield できる値がなくなると、ジェネレータ関数は何もせず単純に終了します。 呼び出し元のコードでは、配列の要素をすべて処理し終えた後のように、そのまま処理が続きます。
注意:
ジェネレータは値を返すことができません。値を返そうとすると、コンパイルエラーになります。 ジェネレータの中で空の return 文を書いても文法上は問題ありませんが、 書いても無意味で、何も起こりません。
ジェネレータ関数の肝となるのが yield キーワードです。 最もシンプルな書きかたをすると、yield 文の見た目は return 文とほぼ同じになります。 ただ、return の場合はそこで関数の実行を終了して値を返すのに対して、 yield の場合はジェネレータを呼び出しているループに値を戻して ジェネレータ関数の実行を一時停止します。
例1 値を yield する単純な例
<?php
function gen_one_to_three() {
for ($i = 1; $i <= 3; $i++) {
// yield を繰り返す間、$i の値が維持されることに注目しましょう
yield $i;
}
}
$generator = gen_one_to_three();
foreach ($generator as $value) {
echo "$value\n";
}
?>
上の例の出力は以下となります。
1 2 3
注意:
内部的には整数の連番のキーが yield する値とペアになり、 配列と同じようになります。
yield を式のコンテキスト (代入文の右辺など) で使うときは、yield 文を括弧で囲む必要があります。 たとえば次のようになります。
$data = (yield $value);
次のように書くと、パースエラーになります。
$data = yield $value;
この構文は、Generator オブジェクトの send() メソッドと組み合わせて使えます。
PHP は、数値添字の配列だけでなく連想配列にも対応しています。ジェネレータも例外ではありません。 先ほどの例のように単なる値を yield するだけでなく、 値と同時にキーも yield することができます。
キーと値のペアを yield する構文は連想配列の定義とよく似ており、次のようになります。
例2 キー/値 のペアの yield
<?php
/* 入力は各フィールドをセミコロンで区切ったものです
* 最初のフィールドが ID となり、これをキーとして使います */
$input = <<<'EOF'
1;PHP;$が大好き
2;Python;インデントが大好き
3;Ruby;ブロックが大好き
EOF;
function input_parser($input) {
foreach (explode("\n", $input) as $line) {
$fields = explode(';', $line);
$id = array_shift($fields);
yield $id => $fields;
}
}
foreach (input_parser($input) as $id => $fields) {
echo "$id:\n";
echo " $fields[0]\n";
echo " $fields[1]\n";
}
?>
上の例の出力は以下となります。
1: PHP $が大好き 2: Python インデントが大好き 3: Ruby ブロックが大好き
先ほどの例のように値だけを yield するときと同様に、 キー/値 のペアを式のコンテキストで yield するときにも yield 文を括弧で囲む必要があります。
$data = (yield $key => $value);
何も引数を渡さずに yield を呼ぶと、NULL
値を yield します。キーは自動的に割り振られます。
例3 NULL
の yield
<?php
function gen_three_nulls() {
foreach (range(1, 3) as $i) {
yield;
}
}
var_dump(iterator_to_array(gen_three_nulls()));
?>
上の例の出力は以下となります。
array(3) { [0]=> NULL [1]=> NULL [2]=> NULL }
ジェネレータ関数は、値を参照として yield することもできます。 関数の結果を参照で返す ときと同じように、関数名の前にアンパサンドを付けます。
例4 参照による値の yield
<?php
function &gen_reference() {
$value = 3;
while ($value > 0) {
yield $value;
}
}
/* $number をループ内で変更していることに注目しましょう。
* このジェネレータは参照を yield するので、
* gen_reference() 内の $value が変わります。*/
foreach (gen_reference() as &$number) {
echo (--$number).'... ';
}
?>
上の例の出力は以下となります。
2... 1... 0...