web-dev-qa-db-ja.com

PHPではyieldはどういう意味ですか?

私は最近このコードを見つけました:

function xrange($min, $max) 
{
    for ($i = $min; $i <= $max; $i++) {
        yield $i;
    }
}

このyieldキーワードを見たことはありません。取得したコードを実行しようとしています

解析エラー:構文エラー、行xで予期しないT_VARIABLE

では、このyieldキーワードは何ですか?有効なPHPでもありますか?もしそうなら、どのように使用しますか?

192
Gordon

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に出会うと、その時点での値を外部ループに返します。次に、ジェネレーター関数に戻り、生成された場所から続行します。 xrangeforループを保持しているため、$maxに到達するまで実行されます。 foreachとピンポンを演奏するジェネレーターのように考えてください。

なぜ必要なのですか?

明らかに、ジェネレータを使用してメモリ制限を回避できます。環境によっては、range(1, 1000000)を実行するとスクリプトが致命的になりますが、ジェネレーターを使用しても同じように動作します。または、ウィキペディアが言うように:

ジェネレータは、生成された値をオンデマンドでのみ計算するため、一度に計算するのが高価または不可能なシーケンスを表すのに役立ちます。これらには、例えば無限のシーケンスとライブデータストリーム。

ジェネレーターも非常に高速であるはずです。しかし、私たちが速い話をしているとき、私たちは通常非常に少ない数で話していることに留意してください。そのため、実行してすべてのコードをジェネレーターを使用するように変更する前に、ベンチマークを実行して、それが理にかなっている場所を確認してください。

ジェネレーターのもう1つのユースケースは、非同期コルーチンです。 yieldキーワードは値を返すだけでなく、値を受け入れます。詳細については、以下にリンクされている2つの優れたブログ投稿を参照してください。

yieldはいつ使用できますか?

ジェネレーターはPHP 5.5で導入されました。そのバージョンの前にyieldを使用しようとすると、キーワードに続くコードに応じて、さまざまな解析エラーが発生します。そのため、そのコードから解析エラーが発生した場合は、PHPを更新してください。

ソースと詳細情報:

286
Gordon

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()generatorb()を返すのはただ1つの単純な配列の違いだけです。両方で繰り返すことができます。

また、最初の配列は完全な配列を割り当てないため、メモリ要求が少なくなります。

25
tsusanka

yieldキーワードは、PHP 5.5の「ジェネレーター」の定義に使用されます。では、 generator ?とは何ですか?

Php.netから:

ジェネレーターは、Iteratorインターフェースを実装するクラスを実装するオーバーヘッドや複雑さなしに、単純なイテレーターを簡単に実装する方法を提供します。

ジェネレーターを使用すると、foreachを使用してコードを記述し、メモリ内に配列を作成することなく一連のデータを反復処理できます。これにより、メモリの制限を超えたり、生成にかなりの処理時間が必要になります。代わりに、ジェネレーター関数を記述することができます。これは、通常の関数と同じですが、1回返す代わりに、ジェネレーターは、反復される値を提供するために必要な回数だけ生成できます。

この場所から:ジェネレーター=ジェネレーター、他の関数(単なる単純な関数)=関数。

したがって、次の場合に便利です。

  • 単純なこと(または単純なこと)を行う必要があります;

    ジェネレーターは、Iteratorインターフェースの実装よりもはるかに単純です。一方、当然のことながら、ジェネレーターの機能は低下します。 それらを比較

  • 大量のデータを生成する必要があります-メモリを節約します;

    実際にメモリを節約するために、ループの反復ごとに関数を介して必要なデータを生成し、反復後にガベージを利用できます。ここでの主なポイントは、明確なコードとおそらくパフォーマンスです。ニーズに合ったものをご覧ください。

  • 中間値に依存するシーケンスを生成する必要があります;

    これは以前の考え方を拡張したものです。ジェネレーターは、関数と比較して物事を簡単にすることができます。 フィボナッチの例 を確認し、ジェネレーターなしでシーケンスを作成しようとします。また、この場合、少なくともローカル変数に中間値を格納するため、ジェネレーターはより高速に動作できます。

  • パフォーマンスを改善する必要があります。

    場合によっては、機能よりも高速に機能します(以前の利点を参照)。

20
QArea

簡単な例

<?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#
19
Think Big

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:無限ループでジェネレーターを使用することは、無限の長さを持つクロージャーを作成するようなものです。 ..

6
inf3rno

ここで議論する価値のある興味深い側面は、参照による譲歩です。関数の外部に反映されるようにパラメーターを変更する必要があるたびに、このパラメーターを参照で渡す必要があります。これをジェネレータに適用するには、アンパサンド&をジェネレータの名前と反復で使用される変数に単に追加します。

 <?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変数への参照です。

0
bodi0