Bonjour. Ce cours va traiter d'un sujet avancé.
Ce que je voudrais vous montrer c'est comment à la
demande, on peut transformer la pile d'exécution en un
objet Pharo et le manipuler. L'idée de ce cours n'est pas
d'aller dans les détails, mais vraiment de vous donner l'intuition.
L'idée, c'est qu'on peut manipuler la pile, on peut la
naviguer, on peut la changer. Je vais essentiellement
vous montrer comment on peut la naviguer.
Il y a 2 chapitres que vous pouvez lire, qui traitent un
petit peu de ce sujet: c'est le chapitre sur les blocks
et le chapitre sur les exceptions du livre "Deep into Pharo".
Pour ceux qui sont intéressés, il faut vraiment les lire
parce que c'est vraiment bien expliqué.
Sinon, vous avez la classe "Context", qui se trouve dans
Pharo, qui représente la pile.
J'ai pris le scénario suivant.
Si vous regardez, on vous a fait coder dans le débuggeur depuis le début.
Qu'est-ce qui se passe dans le débuggeur?
Vous envoyez un message, il n'est pas connu parce que
vous ne l'avez pas défini, vous êtes en train de faire
tourner votre test unitaire, vous arrivez dans le débugger.
Dans le débugger, il vous demande "Voulez-vous créer une méthode?"
Oui, vous créez votre méthode.
Là, le système compile la méthode au vol et l'installe
dans la classe puis relance cette méthode.
Cette méthode, en général, va lever une exception parce
que le système n'est pas magique: avec l'exception
"ShouldBeImplemented", il vous dit que c'est à vous d'implémenter
la méthode dans le débugger.
Vous implémentez la méthode dans le débugger, vous
recompilez cette méthode au vol, vous faites "Proceed",
le système repart et votre programme continue.
En fait si on regarde dans ce scénario, il y a 2 points vraiment importants.
Il y a, d'une part, le fait qu'on a recompilé la méthode
au vol, plusieurs fois d'ailleurs, mais ça c'est le
travail du compilateur, on va dire.
Mais surtout c'est qu'on a transformé et modifié la pile
d'exécution de façon à pouvoir injecter des morceaux de
piles de façon à ce qu'on puisse continuer l'exécution,
alors qu'au départ on avait une erreur.
Ce que je veux vous montrer, c'est que finalement il ne
s'agit pas juste de réifier la pile ou rendre objet la
pile, il ne s'agit pas juste d'un exercice de style, c'est
quelque chose qui va permettre d'améliorer l'expérience
utilisateur et de créer des outils beaucoup plus puissants.
Ce que je vais vous montrer aussi que c'est utilisé dans
Seaside pour servir des applications Web.
En fait, ce qu'il faut voir, c'est que la pile Pharo, en
général c'est une pile C, la machine virtuelle a sa pile,
et que, à la demande, on peut la transformer en un objet vivant.
Ce qui est sympa, c'est qu'on peut naviguer cet objet
vivant, mais aussi qu'on peut le modifier.
Quand je dis le "modifier", ça veut dire que, quand je
vais modifier cet objet "Pharo", ça va modifier la pile C sous-jacente.
Ça, c'est vraiment très puissant.
C'est le support aussi pour toutes les exceptions, c'est
pour ça que je vous suggère de lire le chapitre sur les exceptions.
En effet, on va naviguer cette pile pour chercher les
Block Catch, qu'on appelle les handlers, des
exceptions. Et c'est aussi la transformation de cette
pile en objet permet de construire des continuations, et
donc de faire des serveurs Web comme dans les langages
fonctionnels, tels que Scheme par exemple.
Comment ça marche ?
En fait, Pharo vous propose une variable qui s'appelle thisContext.
En général, on dit que Pharo a 3 pseudo-variables: self,
super et thisContext.
Quand vous demandez la valeur de thisContext, ça va
retourner la pile d'exécution que vous pouvez naviguer.
C'est la pile qui est affichée quand vous ouvrez un
débugger, et c'est basé sur thisContext.
Donc, ce que je vous suggère, c'est par exemple de vous
amuser à définir une petite méthode dans laquelle vous
allez mettre un halt, ça va vous ouvrir un débugger;
mais vous pouvez aussi, dans cette méthode, taper
littéralement thisContext, et vous allez pouvoir l'inspecter
et vous allez avoir un inspecteur sur la pile d'exécution elle-même.
Là, je voudrais vous montrer 2 petits exemples d'utilisation
de thisContext. Le premier, c'est la déprécation.
Quand on veut changer une API en disant "N'utilisez plus
cette API, utilisez cette API", que va-t-on faire en tant que programmeur?
En tant que programmeur, je vais éditer ma méthode et je
vais utiliser le message "Deprecated on in", c'est ce que
je vous avais montré dans le cours sur les exceptions.
Donc là, je veux juste exprimer un petit message "Utilise
Bar". Qu'est-ce que la déprécation va afficher à l'
utilisateur? Elle va afficher "Message foo is deprecated
in Pharo". Il faut voir ici que moi, en tant qu'utilisateur,
je n'ai pas écrit dans quelle méthode j'étais, par
contre, lui il sait identifier que "foo", c'était la méthode appelante.
C'est ça le point, dans les arguments du message
"Deprecated", je n'utilise pas "foo" ou la méthode appelante.
Maintenant, voyons comment c'est implémenter.
Le message "Deprecated" est une exception, deprecation.
Il va prendre les arguments, la chaîne d'explications, la
date et puis la version, mais en plus il va rajouter l'expression
"thisContext sender method".
Qu'est-ce que ça veut dire ?
thisContext, c'est la pile au moment de l'exécution de la
méthode Deprecated. Ça veut dire que là, avec Sender, je
vais pouvoir accéder à la méthode appelante, ça, ça va me
rendre "fou", puisque c'était l'exemple, et je vais
pouvoir lui demander quelle est ta méthode.
Donc, en fait, thisContext sender method, ça va me
retourner la méthode compilée, puisque c'est un objet en
Pharo bien sûr, la méthode compilée à foo.
Et donc l'exception va pouvoir vraiment utiliser ce qu'il
faut et aller extraire le sélecteur de cette méthode,
pour pouvoir faire un message qui soit plus compréhensible.
Donc ça, c'était juste pour faire en sorte que les
messages soient plus compréhensibles pour l'utilisateur
sans forcer le programmeur à coder en dur à chaque fois d'où c'était appelé.
Maintenant, je vais vous montrer une autre fonctionnalité
qui est vraiment extrêmement puissante.
Quand on a un problème, souvent on veut débugger, on
aimerait pouvoir mettre un Breakpoint dans une méthode
qui est extrêmement utilisée. Ça peut être par exemple
utilisé par un Framework, et vous vous voulez juste débugger votre version.
Vous ne voulez pas arrêter tout le système, et les
conditions qu'on a, comme halt once, vont arrêter une
fois, alors que ce que vous voulez dire, c'est que vous n'aimeriez
vous arrêter que si cette méthode a été appelée par cette
autre méthode. Comment exprime-t-on ça?
D'un point de vue des outils du programmeur je vais juste
exprimer ça de la manière suivante: nous allons dire "Je
veux m'arrêter seulement si j'ai été appelé, si foo a été
appelé par la méthode testSetInitialized.
Maintenant, voyons comme on implémente ça.
Vous voyez bien en effet que dans tous les autres cas, il
faut que cette méthode ne s'arrête pas.
Comment donc est-ce implémenté ?
Vous pouvez le voir dans Pharo, je vous invite à ouvrir le code.
Vous avez Halt qui est une exception, et vous avez la méthode "if".
Qu'est ce qu'on fait ?
Elle a plusieurs cas parce que vous pouvez passer un
block, on va juste regarder le cas qui nous intéresse
quand on a un symbole: donc, il regarde la condition
"est-ce que c'est un symbole ?",
si oui, alors regarde si la chaîne d'appel contient le
symbole. Donc regardons.
Voilà, on tombe sur cette méthode-là, que fait cette méthode?
Là, vous imaginez que vous avez "testSetInitialized"
qui est un argument ici.
La première chose qu'elle fait, déjà, c'est de dire "Je
veux récupérer la pile d'exécution", donc Context, en
Pharo c'est souvent synonyme de pile d'exécution, donc
Context, je veux avoir la pile d'exécution.
Et maintenant, je vais faire un WhileFalse, tant que je
ne suis pas en haut de la pile d'exécution… Alors,
comment définir "en haut de la pile exécution"?
Ça veut dire qu'il n'y a plus de Sender, il n'y a plus
personne qui m'invoque. Donc en haut, c'est quand l'invocation
est nil. Donc tant que l'invocation n'est pas nil, je
vais passer d'un morceau de pile à l'autre, donc vous
avez vos morceaux de pile comme ça, conceptuellement au
moins, et vous allez monter de l'un à l'autre, et là vous faites Sender.
Et là on va regarder si le
sélecteur, par exemple quelque part ici je vais avoir
"Test", est-ce que le symbole d'appel de cette
pile correspond à l'endroit où je voudrais m'arrêter, où
j'ai dit "Est-ce que je suis appelé par TestSet?"
Oui, si c'est le cas, je m'arrête, je fais un signal
parce que la classe est une exception.
Et donc, ce dont il faut bien se rendre compte, c'est qu'implémenter
ça dans un langage qui n'a pas de réification de la pile
c'est extrêmement compliqué. Là vous l'avez en 5 lignes.
Dans Pharo, ça paraît un peu complexe peut-être à
comprendre mais c'est super puissant, c'est super compact
et on ne peut faire ça que parce qu'on a cette réification.
Donc ce qu'il faut voir, c'est qu'on ne va pas souvent se
servir de thisContext parce que c'est une fonctionnalité avancée.
Maintenant, c'est super important pour l'innovation.
Ça peut être de l'innovation au niveau des outils, c'est
ce qu'on a montré avec ces tests qui sont extrêmement
compliqués à exprimer dans d'autres langages, mais ça va
être par exemple la possibilité de représenter les continuations.
Et donc c'est pour ça que la personne qui a fait Seaside
l'a fait dans l'ancêtre de Pharo, parce qu'il pouvait
manipuler la pile pour représenter des continuations qui
allaient être la base du mécanisme Call and Answer, qu'on
vous a présentés dans Seaside.
Je vous ai présenté une fonctionnalité assez avancée de
Pharo, vous pouvez vous amuser avec.