On présente ici les blocs1). Les blocs sont des sortes de méthodes anonymes aussi appelées des fermetures lexicales. Elles sont partout dans Pharo.
Elles constituent la base pour les boucles, les conditionnels, les itérateurs et même les traitements graphiques, voire les langages dédiés.
En fait, elles sont au cœur du système, comme nous le verrons. Pour la petite anecdote, elles ont seulement été introduites dans la dernière version de Java.
Pour définir un bloc syntaxiquement, on va utiliser les crochets
[ expr1. expr2. exprn ]
Ici, vous avez un exemple, on a un crochet ouvrant, un ensemble d'expressions et puis un crochet fermant.
On va parler de définition d'un block. Je vais vous prendre un autre exemple. Ici, on a une expression:
" Evaluer cette expression provoque une erreur" ( 1/0 )
Si on exécute cette expression, on voit qu'elle retourne bien une erreur.
Si on encapsule cette expression dans un block:
" Évaluer cette expression ne provoquera pas d'erreur" [ 1/0 ]
Pourquoi? En ligne 2, on définit un block qui contient l'expression 1/0. Si on évalue la définition de ce block, en fait, il ne se passe rien, donc je n'ai pas d'erreur.
Le block nous est retourné, parce qu'en fait la définition d'un block n'exécute pas les expressions qu'il contient. Ici, on peut définir autant de blocks qu'on veut, les expressions à l'intérieur sont comme figées.
Pour évaluer les expressions qu'un block contient, il faut le faire explicitement en lui envoyant le message value.
|aBlock| aBlock := [ 2 + 6 ]. aBlock class. ">>> BlockClosure" aBlock value. ">>> 8"
On a un exemple Ci-dessus, avec un bloc qui contient l'expression “2 + 6”, et puis on lui envoie le message value. Cette fois-ci, les expressions à l'intérieur vont être évaluées et on va nous rendre le résultat: 8. Par contre, si l'une des expressions à l'intérieur du block contient une erreur, évidemment l'erreur va être retournée au moment de l'évaluation du bloc.
C'est le cas de cet exemple.
|aBlock| aBlock := [ 1 / 0 ]. aBlock value. ">>> Erreur ZeroDivide"
Si 1 est divisé par 0, j'envoie le message value au bloc et on a bien une erreur qui est retournée.
Les blocs en fait peuvent aussi avoir des arguments. C'est le cas dans cet exemple comme pour les méthodes.
|aBlock| aBlock := [ :x | x + 2 ]. aBlock value:8 ">>> 10"
Le block est défini, il est toujours ouvert par un crochet ouvrant et fermé par un crochet fermant. On a l'ensemble des arguments qui sont ici préfixés par des deux points. Et puis ensuite, on a une barre verticale, donc un |, qui permet de séparer la partie déclaration des arguments de la partie corps du block, donc l'ensemble des expressions qu'il contient. L'argument s'appelle x dans cet exemple et puis “x + 2”, c'est la seule expression que contient le bloc. Si j'envoie le message value au bloc, il faut utiliser un message particulier donc c'est “value:” avec un paramètre, puisqu'on va lui passer en même temps la valeur 8 qui va se substituer à l'argument x au moment de l'évaluation des expressions dans le bloc. Je lui envoie le message “value: 8”, on va bien avoir le résultat 10 puisque x vaudra 8 pendant l'évaluation de ce bloc.
Un autre exemple proche du précédent sauf que dans ce nouvel exemple, on a plusieurs expressions dans le bloc.
|aBlock| aBlock := [ :x | x + 33. x + 2 ]. aBlock value: 5
Ici, j'en ai 2, donc “x + 33” et “x + 2”. Sauf que quand j'envoie le message “value: 5” pour évaluer les expressions de ce bloc, en fait ce qui va être retourné par l'évaluation, c'est uniquement la valeur de la dernière expression du block. Ici, c'est seulement le résultat de x + 2 qui va être retourné, donc 7.
Les blocs sont des objets comme les autres en Pharo. Ils peuvent être sauvegardés dans des variables temporaires et puis on peut leur envoyer des messages comme pour des objets classiques.
|add2| add2 := [ x: | x + 2 ]. add2 value: 5 ">>> 7" add2 value: 33 ">>> 35"
Là, c'est l'exemple que je vous montre ici. On peut stocker la définition de ce bloc “x + 2” dans une variable qui s'appelle “add2” et puis ensuite, on va lui envoyer des messages pour évaluer plusieurs fois ce bloc.
Je vais lui envoyer une première fois le message “value:” en lui disant de s'évaluer avec la valeur 5, donc ça va nous rendre 7. Et puis une deuxième fois, le message “value:” avec la valeur 33 où ça va nous rendre la valeur 35.
On peut aussi définir des blocs avec plusieurs arguments.
[ :x :y | x + y ] value: 5 value: 7 ">>> 12"
Ici, je vous donne un exemple avec x et y, donc il y a deux arguments pour ce block. Par contre, comment est-ce qu'on pourrait faire pour évaluer ce block, puisqu'il faudrait maintenant passer deux valeurs pour déclencher l'évaluation, à la fois 5 et 7 qui viendraient se substituer à x et y? La réponse, c'est qu'en fait on va utiliser le message value: value: qui est une méthode de la classe block. 5 va bien se substituer à x et 7 va se substituer à y et on aura bien le résultat 12.
Comme pour les méthodes, un bloc peut aussi avoir des variables temporaires. Ici, je vous donne un exemple, mais cette fois, c'est un exemple en conditions réelles puisqu'il est extrait de la classe Collection. Le block commence ici et se termine ici. Ce block a un argument qui s'appelle index. Et puis, il a une variable temporaire définie entre deux barres obliques ici, qui s'appelle args. Cette variable temporaire args n'existe que pendant l'évaluation des expressions du block.
Les blocs sont définis dans des méthodes. Donc dans un bloc, on peut aussi utiliser le return “^”. Je vous donne un exemple ici issu de la classe Integer, c'est la méthode qui s'appelle factorielle.
Integer>>factorial "Answer the factorial of the receiver." self=0 ifTrue:[^1]. self>0 ifTrue:[^self * (self - 1) factorial ]. self error:'Not valid for negative integers'
On a 2 blocs dans cette méthode. Ces blocks contiennent des return, c'est le symbole “^”. Le return va permettre de sortir de la méthode factorielle dans cet exemple. Par exemple, si j'envoie le message factoriel 0, j'obtiens bien la réponse 1 et j'obtiens 1 en fait grâce au return qui dans le premier block qui permet de faire sortir de la méthode factorielle. Le return dans un block permet de sortir de la méthode qui contient la définition du block.
Un petit conseil quand vous utilisez des blocks. Les blocks sont des éléments extrêmement puissants. Il faut les utiliser avec parcimonie.
En résumé, dans cette séquence, on a vu les blocs, on a vu leur syntaxe. On a vu que c'était des sortes de méthodes anonymes, c'est-à-dire des fermetures lexicales. On peut stocker les blocs dans des variables, c'est des objets comme les autres en Pharo.
On verra dans les prochaines séquences que les blocs sont à la base d'énormément de constructions en Pharo: les boucles, les itérations, etc.