私は最近このコードを見つけました:
function xrange($min, $max)
{
for ($i = $min; $i <= $max; $i++) {
yield $i;
}
}
このyield
キーワードを見たことはありません。取得したコードを実行しようとしています
解析エラー:構文エラー、行xで予期しないT_VARIABLE
では、このyield
キーワードは何ですか?有効なPHPでもありますか?もしそうなら、どのように使用しますか?
yield
とは何ですか?yield
キーワード ジェネレーター関数からデータを返します:
ジェネレーター関数の中心はyieldキーワードです。最も単純な形式では、yieldステートメントは関数の実行を停止して戻る代わりに、ジェネレーターをループするコードに値を提供し、ジェネレーター関数の実行を一時停止することを除いて、returnステートメントによく似ています。
ジェネレーター関数は、事実上、よりコンパクトで効率的な Iterator を記述する方法です。 計算して戻る値 while である関数(xrange
)を定義できます ループする :
foreach (xrange(1, 10) as $key => $value) {
echo "$key => $value", PHP_EOL;
}
これにより、次の出力が作成されます。
0 => 1
1 => 2
…
9 => 10
次を使用して、foreach
の$key
を制御することもできます。
yield $someKey => $someValue;
ジェネレーター関数では、$someKey
は、$key
に対して表示したいものであり、$someValue
は$val
の値です。質問の例では、$i
です。
さて、なぜPHPのネイティブ range
function を使用してその出力を達成していないのか疑問に思うかもしれません。そして、あなたは正しい。出力は同じになります。違いは、我々がそこに着いた方法です。
range
PHPを使用する場合、それを実行し、メモリ内の数値の配列全体を作成し、 entire array をreturn
ループに入れてからforeach
ループに渡して値を出力します。つまり、foreach
は配列自体で動作します。 range
関数とforeach
は一度だけ「話す」。パッケージを郵便で受け取るようなものだと考えてください。配達人が荷物を渡して、出発します。そして、パッケージ全体を展開し、そこにあるものをすべて取り出します。
ジェネレーター関数を使用すると、PHPは関数にステップインし、endまたはyield
キーワードのいずれかを満たすまで実行します。 yield
に出会うと、その時点での値を外部ループに返します。次に、ジェネレーター関数に戻り、生成された場所から続行します。 xrange
がfor
ループを保持しているため、$max
に到達するまで実行されます。 foreach
とピンポンを演奏するジェネレーターのように考えてください。
明らかに、ジェネレータを使用してメモリ制限を回避できます。環境によっては、range(1, 1000000)
を実行するとスクリプトが致命的になりますが、ジェネレーターを使用しても同じように動作します。または、ウィキペディアが言うように:
ジェネレータは、生成された値をオンデマンドでのみ計算するため、一度に計算するのが高価または不可能なシーケンスを表すのに役立ちます。これらには、例えば無限のシーケンスとライブデータストリーム。
ジェネレーターも非常に高速であるはずです。しかし、私たちが速い話をしているとき、私たちは通常非常に少ない数で話していることに留意してください。そのため、実行してすべてのコードをジェネレーターを使用するように変更する前に、ベンチマークを実行して、それが理にかなっている場所を確認してください。
ジェネレーターのもう1つのユースケースは、非同期コルーチンです。 yield
キーワードは値を返すだけでなく、値を受け入れます。詳細については、以下にリンクされている2つの優れたブログ投稿を参照してください。
yield
はいつ使用できますか?ジェネレーターはPHP 5.5で導入されました。そのバージョンの前にyield
を使用しようとすると、キーワードに続くコードに応じて、さまざまな解析エラーが発生します。そのため、そのコードから解析エラーが発生した場合は、PHPを更新してください。
Yieldを使用するこの関数:
function a($items) {
foreach ($items as $item) {
yield $item + 1;
}
}
以下のものを除いて、これとほとんど同じです:
function b($items) {
$result = [];
foreach ($items as $item) {
$result[] = $item + 1;
}
return $result;
}
a()
が generator とb()
を返すのはただ1つの単純な配列の違いだけです。両方で繰り返すことができます。
また、最初の配列は完全な配列を割り当てないため、メモリ要求が少なくなります。
yield
キーワードは、PHP 5.5の「ジェネレーター」の定義に使用されます。では、 generator ?とは何ですか?
Php.netから:
ジェネレーターは、Iteratorインターフェースを実装するクラスを実装するオーバーヘッドや複雑さなしに、単純なイテレーターを簡単に実装する方法を提供します。
ジェネレーターを使用すると、foreachを使用してコードを記述し、メモリ内に配列を作成することなく一連のデータを反復処理できます。これにより、メモリの制限を超えたり、生成にかなりの処理時間が必要になります。代わりに、ジェネレーター関数を記述することができます。これは、通常の関数と同じですが、1回返す代わりに、ジェネレーターは、反復される値を提供するために必要な回数だけ生成できます。
この場所から:ジェネレーター=ジェネレーター、他の関数(単なる単純な関数)=関数。
したがって、次の場合に便利です。
単純なこと(または単純なこと)を行う必要があります;
ジェネレーターは、Iteratorインターフェースの実装よりもはるかに単純です。一方、当然のことながら、ジェネレーターの機能は低下します。 それらを比較 。
大量のデータを生成する必要があります-メモリを節約します;
実際にメモリを節約するために、ループの反復ごとに関数を介して必要なデータを生成し、反復後にガベージを利用できます。ここでの主なポイントは、明確なコードとおそらくパフォーマンスです。ニーズに合ったものをご覧ください。
中間値に依存するシーケンスを生成する必要があります;
これは以前の考え方を拡張したものです。ジェネレーターは、関数と比較して物事を簡単にすることができます。 フィボナッチの例 を確認し、ジェネレーターなしでシーケンスを作成しようとします。また、この場合、少なくともローカル変数に中間値を格納するため、ジェネレーターはより高速に動作できます。
パフォーマンスを改善する必要があります。
場合によっては、機能よりも高速に機能します(以前の利点を参照)。
簡単な例
<?php
echo '#start main# ';
function a(){
echo '{start[';
for($i=1; $i<=9; $i++)
yield $i;
echo ']end} ';
}
foreach(a() as $v)
echo $v.',';
echo '#end main#';
?>
出力
#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#
yield
を使用すると、単一の関数で複数のタスク間のブレークポイントを簡単に記述できます。それだけです。特別なことは何もありません。
$closure = function ($injected1, $injected2, ...){
$returned = array();
//task1 on $injected1
$returned[] = $returned1;
//I need a breakpoint here!!!!!!!!!!!!!!!!!!!!!!!!!
//task2 on $injected2
$returned[] = $returned2;
//...
return $returned;
};
$returned = $closure($injected1, $injected2, ...);
Task1とtask2が非常に関連しているが、何かをするためにそれらの間にブレークポイントが必要な場合:
コードを多くのクロージャーに分割したり、他のコードと混合したり、コールバックを使用したりする必要がないため、ジェネレーターが最適なソリューションです。ブレークポイントを追加するにはyield
を使用します。準備ができていれば、そのブレークポイント。
ジェネレータなしでブレークポイントを追加します。
$closure1 = function ($injected1){
//task1 on $injected1
return $returned1;
};
$closure2 = function ($injected2){
//task2 on $injected2
return $returned1;
};
//...
$returned1 = $closure1($injected1);
//breakpoint between task1 and task2
$returned2 = $closure2($injected2);
//...
ジェネレーターでブレークポイントを追加する
$closure = function (){
$injected1 = yield;
//task1 on $injected1
$injected2 = (yield($returned1));
//task2 on $injected2
$injected3 = (yield($returned2));
//...
yield($returnedN);
};
$generator = $closure();
$returned1 = $generator->send($injected1);
//breakpoint between task1 and task2
$returned2 = $generator->send($injected2);
//...
$returnedN = $generator->send($injectedN);
注:ジェネレーターでミスを犯しやすいので、実装する前に必ずユニットテストを作成してください!注2:無限ループでジェネレーターを使用することは、無限の長さを持つクロージャーを作成するようなものです。 ..
ここで議論する価値のある興味深い側面は、参照による譲歩です。関数の外部に反映されるようにパラメーターを変更する必要があるたびに、このパラメーターを参照で渡す必要があります。これをジェネレータに適用するには、アンパサンド&
をジェネレータの名前と反復で使用される変数に単に追加します。
<?php
/**
* Yields by reference.
* @param int $from
*/
function &counter($from) {
while ($from > 0) {
yield $from;
}
}
foreach (counter(100) as &$value) {
$value--;
echo $value . '...';
}
// Output: 99...98...97...96...95...
上記の例は、foreach
ループ内の反復値を変更すると、ジェネレーター内の$from
変数がどのように変更されるかを示しています。これは、ジェネレータ名の前のアンパサンドにより、$from
が参照渡しであるためです。そのため、foreach
ループ内の$value
変数は、ジェネレーター関数内の$from
変数への参照です。