Dans cette séquence, on va voir les itérateurs.
On va revenir sur les collections et voir comment est-ce
qu'on peut les parcourir facilement, grâce aux itérateurs en Pharo.
Vous allez comprendre la puissance des itérateurs en
Pharo, et puis je vais vous donner un panorama des
principaux itérateurs que vous pouvez utiliser sur vos collections
Juste un exemple. Ça, c'est le code que vous devriez
écrire par exemple en Java pour parcourir une collection.
Je veux parcourir une collection "person", pour extraire
la liste des noms de ces personnes, de ces objets "person".
En fait en Pharo, on écrirait plutôt ce code-là, on
utiliserait un itérateur, qu'on reverra dans la suite du
cours, "collect", où on va collecter l'ensemble des noms des personnes.
En Java 8 pour l'anecdote, la dernière version de Java,
ils ont introduit la notion de blocks, l'équivalent des
blocks SmallTalk, les fermetures lexicales, qui leur
permet d'avoir une syntaxe qui est proche de celle de Pharo.
Sauf qu'en Pharo, on l'a depuis très longtemps, depuis le
début et elle est au cœur du langage, ce qui donne une
puissance d'expression aux programmeurs assez importante.
Les itérateurs, il en existe plein.
Le premier qu'on voit, c'est "collect".
A quoi sert collect quand je l'envoie à une collection?
Ici on a une collection de nombres, donc des nombres
positifs et des nombres négatifs. J'envoie le message
"collect" à cette collection et je lui passe un block.
A chaque tour de la collection, le paramètre du block
vaudra successivement 2, moins 3, 4, et caetera.
Et puis on va envoyer le abs, donc abs c'est absolu.
Donc ça veut dire qu'on va demander la valeur absolue de ce nombre.
Une fois qu'on aura appliqué ce block à chacun des
éléments de la collection, on va agréger tous les
résultats dans une nouvelle collection.
On voit que le résultat rendu par collect c'est une
nouvelle collection, telle qu'on a appliqué ce block à
chacun des éléments de la collection.
On a pris la valeur absolue de 2; la valeur absolue de
moins 3, donc 3; la valeur absolue de 4, 4; la valeur
absolue de moins 35, ça fait 35 et la valeur absolue de 4 ça fait 4.
Le point à retenir qui est vraiment intéressant, c'est qu'on pense objet.
On demande à la collection de faire quelque chose pour
nous, donc c'est la collection qui va se parcourir les
éléments qu'elle contient elle-même et on va lui fournir
le traitement à appliquer sur chacun des éléments.
C'est là qu'est le secret des itérateurs.
Voici un nouvel exemple de collect.
J'ai une collection ici, je lui envoie le message
"collect" et dans le block je vais à chaque fois demander
"Est-ce que l'élément est impair ?".
Et donc ici pour le premier élément, je vais agréger tous
les résultats: false, true, false et true.
Vous pouvez naturellement écrire ce qu'on aurait l'habitude
d'écrire dans notre langage où les blocks et les
itérateurs n'existent pas, donc on pourrait écrire "Je
prends la collection, je vais construire une collection
de résultats et je vais parcourir de 1 à la taille de la
collection, collection size.
Je vais utiliser un do.
Et puis je vais parcourir la collection et à chaque fois
ajouter dans la collection le résultat.
" On peut écrire tout ça et c'est complètement équivalent à ça.
A vous de choisir votre camp: est-ce que vous voulez
écrire quelque chose de simple ou quelque chose de plus compliqué?
En gros, la question est là.
Personnellement, je préfère clairement cette première solution.
Il existe dans la hiérarchie des collections Pharo
quelque chose de fondamental, c'est que toutes les
collections sont polymorphiques et héritent de la classe
collection et on a une API commune.
L'avantage, c'est que les itérateurs aussi vont
fonctionner sur la plupart des collections.
L'idée, c'est vraiment de penser objet.
On demande à la collection d'itérer sur ces éléments pour
nous, puisque on ne sait pas si on est en train de
manipuler un dictionnaire ou on n'a pas envie de savoir
comment sont représentées les clefs, les valeurs, et cetera.
Donc on va vraiment demander "Collection, parcours-toi et
applique ce traitement sur tes éléments.
"
Il existe beaucoup d'itérateurs qui vont permettre de faire ça.
On a vu le do, le collect.
On en a d'autres: select, rejet, detect, et caetera, on
va en voir quelques uns dans la suite de cette séquence.
Le do, c'est l'itérateur le plus simple qui permet de
parcourir chacun des éléments de ma collection et je les
affiche ici dans le Transcript. Celui-là, on l'a déjà vu
plein de fois dans les autres exemples.
Un nouvel itérateur qui est select.
Je veux récupérer tous les éléments de la collection qui
répondent à un critère donné.
Ici, je veux tous les éléments impairs de la collection.
J'envoie "select" à la collection, je passe un block et à
chaque fois que ce bloc va s'évaluer à "Vrai", l'élément
en question sera ajouté dans la collection résultats.
C'est complètement équivalent à select: odd.
Quand j'ai un un bloc ici ou ce qui constitue seulement
un envoi de message à l'élément de la collection, le
paramètre du block, je peux mettre directement sous la
forme d'un symbole le nom du message à envoyer.
C'est encore plus concis et ça ne marche que pour les messages unaires.
On peut utiliser d'autres types d'itérateurs comme reject.
Je veux éliminer tous les éléments de la collection qui
sont impairs et donc, dans la collection résultats, il ne
va me rester que des éléments pairs.
Ou alors, je veux faire un detect, je veux détecter le
premier élément de la collection qui répond à un critère
donné pour lequel l'évaluation du bloc va être vraie.
Je veux le premier élément de la collection qui est impair, donc 11.
Dans certains cas, on veut détecter le premier élément de
la collection qui répond à un critère, mais s'il n'y en a
pas alors on aimerait bien avoir une valeur par défaut.
C'est "detect: ifNone", donc s'il n'y a aucun élément c'est
l'évaluation de ce block là qui va être effectuée, et
donc ça va bien nous renvoyer 0.
Il y a d'autres itérateurs encore qui vont encore
faciliter la vie du programmeur. Par exemple, le "anySatisfy".
Est-ce qu'il existe dans la collection un élément qui
répond à ce critère-là? Je veux tous les éléments d'une
collection qui répondent à un critère donné.
Je veux parcourir la collection à l'envers en partant de
la fin vers le début. Je veux parcourir la collection en
ayant un curseur d'index. Je veux parcourir les éléments
de la collection 2 à 2.
Je veux parcourir toutes les permutations circulaires
possibles des éléments d'une collection.
Il y en a beaucoup des itérateurs et on peut en
construire des nouveaux, d'ailleurs. Ici, je veux
parcourir une collection 1, 2, 3 couplée avec une deuxième collection.
Dans mon bloc do, j'aurai x et y, 2 paramètres.
Le premier x, ça sera un élément de la première
collection, et y un élément de la deuxième collection.
Donc je vais pouvoir multiplier ces éléments entre eux.
On obtient bien 10, 40 et 90.
Et bien évidemment, il faut absolument que les 2
collections aient la même taille avec cet itérateur-là.
On peut avoir d'autres types de parcours.
Ici, j'utilise le "do separatedBy".
J'ai une collection, je vais parcourir chacun des
éléments et à chaque fois que j'ai parcouru un élément,
je vais évaluer un bloc ici qui correspond à l'affichage d'une virgule.
Ça va me permettre de parcourir le A, afficher une
virgule, afficher le B, afficher une virgule, afficher le C.
A chaque fois entre chaque élément, je vais avoir effectué une action.
Ici, j'ai un itérateur qui est "GroupBy", qui me permet
de grouper les éléments d'une collection en fonction d'un critère.
J'envoie ce message à la collection 1, 2, 3, 4, 5, 6, 7
ici et je récupère en résultat un dictionnaire.
Donc tous les éléments qui ont répondu "Faux" à ce
critère, le critère c'était "even" c'est les éléments
pairs, donc on voit que ça contient bien une collection
de tous les éléments impairs. Et tout ce qui a répondu
"Vrai", c'est tous les éléments
pairs.
Quand on a une collection souvent quand on fait des
calculs on a tendance à imbriquer des collections dans
des collection, et on se retrouve avec des niveaux d'imbrication
qui peuvent être importants.
Ici, vous avez un exemple construit à la main où on a des
collections qui sont imbriquées dans des collections.
Ce qu'on aimerait c'est arriver à aplatir la collection,
à mettre tous les éléments au même niveau.
Pour ça, on a quelque chose de facile en Pharo, on a un
itérateur qui s'appelle "flatCollect".
C'est-à-dire que je vais parcourir les éléments et
construire une nouvelle collection dans laquelle j'ai tout aplati.
Donc on obtient bien la collection 1, 2, 3, 4, 5, 6 dans
laquelle on a enlevé tous les niveaux d'imbrication.
Le secret, ce n'est pas de vous présenter tous les
itérateurs disponibles dans Pharo, ce serait long et fastidieux.
L'idée, c'est vraiment de vous montrer qu'il en existe
plein et que vous pouvez découvrir vraiment les vôtres en
allant lire les méthodes qui existent sur les classes des
collections, en allant découvrir ces méthodes.
Un exemple simple, c'est de commencer par les itérateurs
que vous connaissez. Par exemple, se poser la question
"Comment est-ce qu'est implémenté le do?"
Je cherche le do dans la hiérarchie des collections, je
vais voir qu'il est implémenté dans
"SequenceableCollection", toutes les collections séquençables.
Donc la méthode "do", elle prend en paramètre un block.
Et voici l'implémentation de cette collection.
1 a la taille de la collection do et j'ai un block.
Je vais évaluer le block qui est passé en paramètre en
lui passant l'élément à l'indice I.
Donc c'est tout simple.
Les itérateurs sont extrêmement puissants en Pharo, comme
on a pu le voir. Toutes les collections supportent ces
itérateurs de façon polymorphique.
Du point de vue programmeur, on utilise des itérateurs et
puis c'est chacune des classes de collections qui vont
les implémenter de façon adéquate par rapport à la
collection qu'ils représentent.
On peut en définir des nouveaux, extrêmement intéressants.
Je peux définir mes propres itérateurs si j'en ai envie
sur les classes de collections.
Il y a une subtilité.
Pour ceux qui connaissent le Design pattern iterator, en
fait la différence c'est que le développeur ne contrôle
pas quand il passe à l'élément suivant.
C'est la collection, qui en interne, décide de passer à l'élément suivant.
On n'envoie pas explicitement le message "next" à l'itérateur.
C'est une subtilité pour ceux qui connaissent le Design pattern iterator.
En résumé, les itérateurs sont vraiment puissants et
un allié fort du programmeur qui vont faciliter l'écriture des programmes.
On l'a vu, ça permet d'écrire du code concis, simple et
élégant et ça permet de garantir l'
encapsulation des données au sein d'une collection.