En PHP: quelle est la différence entre «return», «yield», «yield from» et mélanger à la fois yield et return dans la même fonction?

10

La différence entre returnet yieldsemblait claire jusqu'à ce que je comprenne qu'il y avait aussi yield fromet la possibilité de combiner les deux returnet yielddans la même fonction!

D' returnaprès ce que je comprends, tout ce qui s'est passé n'a pas été exécuté, n'est-ce pas?

Toutefois:

function generate(): iterable {
    return [1, 2, 3];
}

foreach (generate() as $value) {
    echo $value;
}

Produit: "123"

Mais ce qui suit:

function generate(): iterable {
    return [1, 2, 3];
    yield;
}

foreach (generate() as $value) {
    echo $value;
}

Ne produit rien! Cela signifie donc que le rendement est exécuté?

Est-ce un bug?


la source
1
var_dump(generate()->GetReturn());
AbraCadaver

Réponses:

10

Return

Redonne simplement une valeur unique à l'appelant.

Yield

Transformez la fonction / méthode actuelle pour renvoyer a Generator, qui produira plus qu'une valeur unique: à chaque yielddéclenchement, elle donne la valeur à l'appelant, une à la fois, en utilisant traditionnellement une foreachboucle.

Yield + Return

Les générateurs, en plus de générer des valeurs, peuvent également fournir une valeur renvoyée unique. Cette valeur ne fera pas partie du bouclage autour du générateur, elle doit être accessible en utilisant la Generator::getReturn()méthode.

Return + Yield

Cela pourrait être vu comme un bug, mais ce n'est pas le cas.

Ce sont deux phases:

  1. Du code au bytecode : pendant cette phase, la generate()fonction est vue comme contenant le yieldmot - clé, elle est donc marquée comme produisant un Generator.
  2. Exécution : Étant donné que le returnse trouve avant le yield, le générateur n'a pas la possibilité de produire de valeur. Cependant, le [1, 2, 3]tableau peut être récupéré avec Generator::getReturn().

Un exemple annoté complet:

// Generate integers 1 and 2
function generateIntegers1And2(): Generator {
    yield 1;                                  // <--+   <--+   <--+
    yield 2;                                  //  <-+    <-+    <-+
}                                             //    |      |      |
                                              //    |      |      |
foreach (generateIntegers1And2() as $value) { //    |      |      |
    var_dump($value); // Shows 1, then 2          ->*      |      |
}                                                       // |      |
                                                        // |      |
function generateOuterYield(): Generator {              // |      |
    // Yields the generator *itself* returned by           |      |
    // generateIntegers1And2() not the actual values       |      |
    // generated by it.                                    |      |
    // This means we are producing here a generator        |      |
    // of generator of integers.                           |      |
    yield generateIntegers1And2();          // <-+         |      |
}                                             // |         |      |
                                              // |         |      |
foreach (generateOuterYield() as $value) {    // |         |      |
    var_dump($value);                       // ->*         |      |
    // The two levels of imbrication means we have         |      |
    // to loop once more to actually consume               |      |
    // generateIntegers1And2                               |      |
    foreach ($value as $val) {                          // |      |
        var_dump($val); // Shows 1, then 2               ->*      |
    }                                                          // |
}                                                              // |
                                                               // |
// A generator can just be returned as-is:                        |
function generateOuterReturn(): Generator {                    // |
    return generateIntegers1And2();                            // |
}                                                              // |
                                                               // |
// it doesn't change the way it is consumed                       |
foreach (generateOuterReturn() as $value) {                    // |
    var_dump($value); // Shows 1, then 2                          |
}                                                              // |
                                                               // |
function generateOuterYieldFrom(): Generator {                 // |
    // First yield values generated by generateIntegers1And2()    |
    yield from generateIntegers1And2();                        // *<---+
    // then yield integers 3                                           |
    yield 3;                                                     // <--+
    // and 4                                                           |
    yield 4;                                                     //  <-+
}                                                                //    |
                                                                 //    |
foreach (generateOuterYieldFrom() as $value) {                   //    |
    var_dump($value); // Shows 1, 2, 3 and 4                         ->*
}

function generateIntegers56AndReturn(): Generator {
    yield 5;                                                  // <---+
    yield 6;                                                  //  <--+
                                                              //     |
    return ["five", "six"];                       // <--+            |
}                                                 //    |            |
                                                  //    |            |
$gen = generateIntegers56AndReturn();             //    |            |
                                                  //    |            |
// Consume the values **yielded** by                    |            |
// generateIntegers56AndReturn()                        |            |
foreach ($gen as $value) {                        //    |            |
    var_dump($value); // Shows 5, then 6                |          ->*
}                                                 //    |
                                                  //    |
// Access the value **returned** by the generator       |
var_dump($gen->getReturn());                      //  ->*

function wtf(): Generator {
    return ["W", "T", "F", "!"];
    // Without the following line, PHP would complain with a TypeError:
    // Return value of wtf() must be an instance of Generator, array returned.
    // The presence of a yield keyword anywhere inside the function makes it a Generator.
    // However, since we return *before* reaching any *yield*, 42 is never yielded.
    // This is empty generator!
    yield 42;
}

$gen = wtf();

// This foreach loop is not entered!
foreach ($gen as $value) {
    var_dump($value);
}

// However, we can loop on the array *returned* by wtf():
foreach ($gen->getReturn() as $value) {
    echo $value; // Will print: WTF!
}

la source
1
Le dernier exemple, le retour "termine" l'exécution de la fonction, c'est-à-dire que le code n'atteint pas le résultat.
Rodrigo Jarouche
5

De la documentation :

Toute fonction contenant yieldest une fonction de générateur.

Peu importe que le yieldsoit exécuté, l'analyseur le voit quelque part dans la définition de la fonction et le transforme en générateur.

Si la fonction n'exécute jamais l' yieldinstruction, le générateur ne produit aucune valeur. La valeur renvoyée par returnest ignorée lorsque vous essayez d'utiliser le résultat. La documentation dit:

Remarque:
En PHP 5, un générateur ne pouvait pas retourner une valeur: cela entraînerait une erreur de compilation. Une returninstruction vide était une syntaxe valide dans un générateur et elle mettrait fin au générateur. Depuis PHP 7.0, un générateur peut retourner des valeurs, qui peuvent être récupérées en utilisant Generator :: getReturn () .

Vous pourriez donc faire:

$gen = generate();
foreach ($gen as $value) {
    echo $value;
}
print_r($gen->getReturn());
Barmar
la source