PHP introduit les générateurs
Par un mécanisme similaire à celui de Python avec le mot-clé yield
Le 2012-09-04 22:05:09, par tarikbenmerar, Chroniqueur Actualités
Les générateurs sont un moyen simple et puissant de créer des itérateurs dans des langages tels que Python. Maintenant, c'est PHP qui fait le pas et s'approprie ce concept.
Pour comprendre l'utilité et la puissance de ce dernier, on revoit l'exemple typique de lecture d'un fichier en entier :
Le point faible de ce code est le fait qu'il copie tout le fichier dans un grand tableau. Ainsi, plus le fichier est grand, plus le besoin en mémoire s'accroît, avec un risque imminent d'atteindre les limites. Il faut toujours se rappeler qu'un script PHP doit respecter une limite de mémoire spécifiée par l'administrateur.
On peut certainement éviter ce comportement et récupérer les données ligne par ligne, en utilisant les itérateurs qui sont parfaits pour ce cas d'utilisation. Malheureusement, en PHP il n'existait jusque-là aucune manière simple d'implémenter les itérateurs. Pour y arriver, on est amené à créer une classe complexe implémentant une interface Iterator comme suit :
Pour un cas aussi simple que notre exemple, le code est déjà complexe. Avec les générateurs, on peut considérablement réduire le nombre de lignes de code de manière directe à partir de la première version du code :
La toute petite différence réside dans l'utilisation du mot clé yield, qui génère une nouvelle donnée dans l'itération.
En effet, l'instruction $lines = getLinesFromFile($fileName) ne renvoie aucune donnée, c'est simplement un générateur qui implémente l'itérateur qui vient d'être créé.
Après, pendant l'exécution de la boucle foreach ($lines as $line), chaque itération génère les données renvoyées par yield, qui seront stockées dans $line. En fait, cette génération implémente un objet Iterator et des appels à Iterator::next() seront effectués. L'exécution s'arrête jusqu'à la rencontre du prochain yield, qui renvoie la prochaine donnée, et ainsi de suite...
Quelques jours après l'introduction du mot clé Finally, se succèdent donc pour PHP les bonnes nouvelles. Ou les emprunts d’autres langages, diront certains.
Source : détails du mot clé yield dans le site de PHP
Et vous ?
Quelle impression vous laisse cette annonce ?
Pouvez-vous trouver d'autres cas d'utilisation intéressants ?
Quelle autre approche de simplification des itérateurs auriez-vous préférée pour PHP ?
Pour comprendre l'utilité et la puissance de ce dernier, on revoit l'exemple typique de lecture d'un fichier en entier :
Code : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | function getLinesFromFile($fileName) { if (!$fileHandle = fopen($fileName, 'r')) { return; } $lines = []; while (false !== $line = fgets($fileHandle)) { $lines[] = $line; } fclose($fileHandle); return $lines; } $lines = getLinesFromFile($fileName); foreach ($lines as $line) { // do something with $line } |
On peut certainement éviter ce comportement et récupérer les données ligne par ligne, en utilisant les itérateurs qui sont parfaits pour ce cas d'utilisation. Malheureusement, en PHP il n'existait jusque-là aucune manière simple d'implémenter les itérateurs. Pour y arriver, on est amené à créer une classe complexe implémentant une interface Iterator comme suit :
Code : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | class LineIterator implements Iterator { protected $fileHandle; protected $line; protected $i; public function __construct($fileName) { if (!$this->fileHandle = fopen($fileName, 'r')) { throw new RuntimeException('Couldn\'t open file "' . $fileName . '"'); } } public function rewind() { fseek($this->fileHandle, 0); $this->line = fgets($this->fileHandle); $this->i = 0; } public function valid() { return false !== $this->line; } public function current() { return $this->line; } public function key() { return $this->i; } public function next() { if (false !== $this->line) { $this->line = fgets($this->fileHandle); $this->i++; } } public function __destruct() { fclose($this->fileHandle); } } $lines = new LineIterator($fileName); foreach ($lines as $line) { // do something with $line } |
Code : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function getLinesFromFile($fileName) { if (!$fileHandle = fopen($fileName, 'r')) { return; } while (false !== $line = fgets($fileHandle)) { //c'est ici la différence yield $line; } fclose($fileHandle); } $lines = getLinesFromFile($fileName); foreach ($lines as $line) { // do something with $line } |
En effet, l'instruction $lines = getLinesFromFile($fileName) ne renvoie aucune donnée, c'est simplement un générateur qui implémente l'itérateur qui vient d'être créé.
Après, pendant l'exécution de la boucle foreach ($lines as $line), chaque itération génère les données renvoyées par yield, qui seront stockées dans $line. En fait, cette génération implémente un objet Iterator et des appels à Iterator::next() seront effectués. L'exécution s'arrête jusqu'à la rencontre du prochain yield, qui renvoie la prochaine donnée, et ainsi de suite...
Quelques jours après l'introduction du mot clé Finally, se succèdent donc pour PHP les bonnes nouvelles. Ou les emprunts d’autres langages, diront certains.
Source : détails du mot clé yield dans le site de PHP
Et vous ?
-
transgohanExpert éminentJe trouve qu'avec l'exemple donnée on perd en lisibilité et compréhension.
Aucun retour de fonction mais il y en a bel et bien un de par l'implication de yield.
Ils viennent de créer un second mot clé de retour de fonction mais que je ne trouve pas très pratique.
J'ai parcouru la rfc et j'ai trouvé d'autres exemples qui me chagrinent comme celui là :
Code : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function echoLogger() { while (true) { echo 'Log: ' . yield . "\n"; } } function fileLogger($fileName) { $fileHandle = fopen($fileName, 'a'); while (true) { fwrite($fileHandle, yield . "\n"); } } $logger = echoLogger(); // or $logger = fileLogger(__DIR__ . '/log'); $logger->send('Foo'); $logger->send('Bar');
Voilà pour mes deux sous de réflexions.le 05/09/2012 à 10:38 -
spidermarioMembre éprouvéEn fait, pour l’exemple donné, une autre était déjà possible :
Code php : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function forEachLineFromFile($fileName, $f) { if (!$fileHandle = fopen($fileName, 'r')) { return; } while (false !== $line = fgets($fileHandle)) { $f($line); } fclose($fileHandle); } forEachLineFromFile($filename, function($line) { // do something with $line });
C’est proche de l’approche employée, par exemple, par Scheme et sa fonction call-with-input-file.le 05/09/2012 à 0:57 -
camus3Membre éprouvéC'est bien mais on aimerai aussi que de vieux "bugs" soient corrigés , hein
http://phpsadness.com/
mais toute nouveauté est bonne à prendre.le 05/09/2012 à 12:35 -
pcdwarfMembre éprouvéJe m'étonne du code impératif donné en exemple avec pour argument principal que ça fait charger tout le fichier en ram et que c'est moche.
En effet Il est inutile et même néfaste de charger tout le fichier en ram si on peut le traiter ligne à ligne.
Sauf qu'il n'y a pas besoin d'usine à gaz pour traiter un fichier en une seule passe.
L'exemple deviensCode : 1
2
3
4
5
6
7
8
9
10
11
12
13function dosomethingwhithFile($fileName, $linefunction) { if (!$fileHandle = fopen($fileName, 'r')) { return false; } while (false !== $line = fgets($fileHandle)) { $linefunction($line); } fclose($fileHandle); return true; }
Au cas ou ça ne serait pas clair, je ne critique pas le nouveau "yield" mais simplement que le problème soulevé dans l'exemple et prétendument résolu par ce nouvel élément relève largement plus d'une mauvaise approche que d'une limitation du langage.le 05/09/2012 à 19:21 -
elderionMembre régulierPar rapport a la syntaxe et sa lisibilité :
si je fais une analogie avec un autre langage qui utilise yield :
C# utilise bien "yield return" et pas juste "yield".
J'aurai trouvé plus commode que PHP s'inspire de la syntaxe C# :
de considérer yield comme un complément du return et pas comme un remplacant.le 06/09/2012 à 11:17 -
elderionMembre régulier(ceci reste mon strict point de vue)
j'ai fait quelques pas vers Python et meme Ruby :
sans meme parler de puissance et tout ca
rien que la syntaxe du langage me rebute, ca ressemble a du "vieux code".
Avec des "def", des __INIT__, puis l'encapsulation optionnelle.
C'est pas pour troller, juste expliquer pourquoi je suis réfractaire a Python.
C'est sans doute une question d'affinité, car j'aime la syntaxe Java ou PHP avec des accolades et des points virgule, etc...
Sans ca j'aurais surement adopté Python ou Ruby.le 06/09/2012 à 15:42 -
CyberDenixMembre à l'essaiJe vais faire mon captain obvious, mais...
Code php : 1
2
3
4
5
6
7
8
9
10while (($line = fgets($fileHandle)) !== false) { // Il suffit de traiter la ligne directement ici // -> Consommation mémoire quasi constante // -> Pas besoin d'un yield avec post processing super lent doSomething($line); }
Si c'est juste pour traiter le fichier ligne par ligne, les itérateurs ou le yield me semblent hors de propos.
Mais je loupe peut-être le sens caché de cette formidable avancée technologique ?
Edit : Grilled by spidermario et pcdwarf. J'me sens moins seul !le 17/09/2012 à 22:46 -
Lupus MichaelisMembre du ClubJ'avais été enchanté de découvrir les générateurs avec Python. Mais c'est avec de la pratique qu'on peut vraiment juger de la pertinence d'une telle fonctionnalité. Donc je vais attendre de pester contre les bogues (et trous de sécurité) qu'aura introduit ce nouveau gadget.le 05/09/2012 à 7:49
-
_skipExpert éminentCe n'est pas mal à voir comme ça.
Mais déléguer un code de lecture de fichiers à une autre méthode, ça impliquerait presque une gestion d'erreur par exception. Si fgets retourne false, ça ne veut pas dire que la fin du fichier a été atteinte, ça peut être une erreur IO.le 05/09/2012 à 8:32 -
Grimly_oldMembre avertiCe mot clé est un raccourci pour cette structure :
Code : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31public class Resultat extends Exception { var $resultat; public function __constructor($resultat) { $this->resultat = $resultat; } public function getResultat() { return $this->resultat; } } function monIterateur() { $resultat = calculs(); throw new Resultat($resultat); } function utilisation() { do { try { monIterateur(); } catch (Resultat $r) { $line = $r.getResultat(); $continue = $line === FALSE; //On suppose que nos calculs nous retournent FALSE à la fin de lecture. if ($continue !== FALSE) { doStuff($line); } } } while ($continue); }
Je trouve donc ce mot clé comme un sucre syntaxique inutile.le 05/09/2012 à 10:29