• Aucun résultat trouvé

laintenant on souhaite ajouter une seconde opération au lan- gage : aǦcher une expression (pretty-printing).

show : Term -> String

ruivant la décomposition par objets, il faut ajouter l’opération sur chaque objet qui représente le terme. cans des langages à ob- jets qui ne permettent pas l’extension, une façon de faire serait de modiǤer le code source des objets en question. b’est la tension qui

. . Ajouter des opérations harouelle

est au cœur du problème de l’expression (3.6). dn iavarcript, les objets sont extensibles, donc ajouter une opération peut se faire sans modiǤer le code des objets eux-mêmes :

1 num.show = function() { 2 return this.n.toString()

3 }

4

5 plus.show = function() {

6 return this.l.show() + '+' + this.r.show()

7 }

8

9 plus.new(num.new( ), num.new( )).show() //: " + " 10 e .show() //: " "

11 e .show() //: " + "

kigne 1, on ajoute l’opérationshowau termenum; même chose pourplusligne 5. nn voit qu’on peut appliquer la nouvelle opéra- tionshowsur un terme de la même façon queev6l(ligne 9).

lais les lignes qui suivent montrent que cette nouvelle opé- ration est aussi disponible sur les termes créés avant l’ajout de l’opération. kes variables e et e sont les termes créés dans les exemples précédents. Au moment de leur création l’opérationshow

n’était pas déǤnie, et pourtant elle s’applique correctement à ces instances.

num

e1

objet global

l

plus

e2

r

show...

show...

...

Aǣecter les instances existantes de termes est rendu possible par le mécanisme de délégation. ka délégation est illustrée dans la représentation ci-contre. nn y voit l’objete qui a pour prototype l’objetplus; le prototype permet d’établir un lien de délégation en iavarcript. puand on appelle l’opérationshowsure , elle n’est pas présente sur l’objet lui-même, mais sur son prototype. k’opération est donc trouvée et appliquée à l’objet. ce même pour les sous- termese .lete .rqui vont chercher l’opérationshowde l’objet

num.

kes deux mécanismes qui permettent l’extension des opérations sont donc : la délégation, et la possibilité d’ajouter des fonctions aux objets après leur création. rans la délégation, l’objete contien- drait une copie de chaque opération après sa création ; il n’y aurait plus de lien entrenumet ses instances sur toute la durée de vie du programme. dn conséquence, ajouter une opération ànumn’aǣec- terait pas les instances déjà créées. ri on souhaitait ajoutershow

à ces instances, il faudrait les cataloguer pour pouvoir toutes les modiǤer. ce même, si les objets iavarcript étaient Ǥxes après leur création, on ne pourrait pas leur ajouter de fonction. oour étendre le langage, il faudrait créer un nouvel objetnum qui contiendrait l’opérationshow, et auraitnumcomme prototype.

oouvoir ajouter une opération rétroactivement peut être pra- tique dans un système évolutif. nn peut étendre un interpréteur sans avoir à le relancer, ni même reconstruire l’arbre syntaxique

harpaillons Construire un interpréteur par modules

d’un programme. nn peut enrichir la sémantique du langage à l’exécution, et évaluer un même programme pour obtenir diǣé- rents résultats.

c’un autre côté, on peut aussi vouloir préserver la transparence référentielle et ne pas aǣecter les termes existants. b’est possible par simple délégation : on crée des dérivés denumetplusqui pos- sèdent l’opération show. nn ne peut pas donner à ces dérivés les même noms que les objets originaux, donc on utilise un nouvel ob- jet en tant qu’espace de noms. nn ne souhaite pas non plus coupler prématurément les dérivés de numetplusaux objets dont ils dé- rivent ; on abstrait donc ces objets parents à l’aide d’une fonction.

num e1 objet global plus e2 show ... ... ... s ... plus num show

Ajouters n’aǣecte ni e ni e : c’est un nouvel objet qui réutilisenum et plus sans les modiǤer.

1 v6r show = function(b6se) {

2 v6r num = {__proto__: b6se.num,

3 show() { return this.n.toString() }} 4

5 v6r plus = {__proto__: b6se.plus, 6 show() {

7 return this.l.show() + '+' + this.r.show()

8 }}

9

10 return {__proto__: b6se, num, plus} 11 }

12

13 v6r s = show({num, plus}) 14 e .show //: undefined

15 s.plus.new(s.num.new( ), s.num.new( )).show() //: " + " 16 s.plus.new(s.num.new( ), s.num.new( )).ev6l() //:

ke code émule un système de modules :show ligne 1 est une fonction qui prend un module de base et retourne un module en- richi avec l’opérationshow. kes modules sont ici de simples objets iavarcript. k’objetb6sepassé en argument doit contenir les termes du langage :b6se.numetb6se.plus, qui sont utilisés comme pro- totypes des nouveaux objetsshow.numetshow.plus(lignes 1 et 2). bes nouveaux objets contiennent juste l’opérationshowdéǤnie comme précédemment. dnǤn, la fonction showretourne un nou- veau module (un objet) qui contient les versions enrichies denum

etplus(ligne 10).

be module hérite du moduleb6se passé en argument pour des raisons qui sont

explicitées enA.3. tne fois cette fonction déǤnie, on peut instancier le module show dans la variable s par l’appel ligne 13. ri on ne considère pas les modiǤcations de l’exemple précédent, le termee ne peut pas répondre à l’opération show(ligne 14). dn revanche, une ex- pression construite à partir des nouveaux termess.plusets.num

supporte l’opérationshow(ligne 15). dt par délégation, ce nouveau terme supporte aussi l’opérationev6l(ligne 16). kes termes exis- tants ne sont donc pas aǣectés par l’ajout de l’opérationshow, et les nouveaux termes supportent les deux opérations.

lais il y a maintenant un problème d’interopérabilité entre les deux variantes du langage. nn peut construire des expressions qui

. . Ajouter des opérations havi

mélangent les termes de base et les termes du module ’show’ :

s.plus.new(num.new( ), s.num.new( )).show()

ke premier argument des.plus.newest unnumet le second un

s.num. ri on appelle l’opérationshowsur cette expression, le pro- gramme lève une erreur car l’opérationshown’est pas déǤnie sur

num. ka déǤnition deplus.showrequiert quethis.lsupporte éga- lement l’opérationshow. conc, le constructeur de termess.plus

ne devrait accepter que des termes du langage enrichi : dess.plus

ou dess.num. nr, on ne modiǤe pas la déǤnition du constructeur pour restreindre les objets acceptés.

ke constructeur s.plus.new est le même que plus.new, le constructeur de base :

plus.new : Term -> Term -> Term s.plus.new : Term -> Term -> Term

mais la déǤnition des.plus.showimplique en réalité le type :

Term with Show -> Term with Show -> Term with Show

oùwith Showindique qu’il s’agit d’un terme qui supporte l’opé- rationshow.

nn peut empêcher la création d’expressions mixtes par construc- tion. hl suǦt de n’autoriser qu’un seul module de termes à la fois, en masquant les références vers les num etplus originaux. b’est possible avec la constructionwithde iavarcript.

ka construction with en iavarcript a pour but de réduire le bruit syntaxique d’un code qui fait référence plusieurs fois aux pro- priétés d’un même objet. cans la situation suivante :

c6nv6s.begin() c6nv6s.setColor(...) c6nv6s.dr6wRect6ngle(...) c6nv6s.setColor(...) c6nv6s.dr6wCircle(...) c6nv6s.finish()

nn peut utiliserwithpour écrire plutôt :

with (c6nv6s) { begin() setColor(...) dr6wRect6ngle(...) setColor(...) dr6wCircle(...) finish() }

ka sémantique de with est de prendre l’objet passé en argu- ment (c6nv6s) et d’en faire un environnement qui sera utilisé pour résoudre les noms de variables du corps duwith. cans l’exemple, le nom begin est résolu en examinant d’abord l’environnement

havre Construire un interpréteur par modules

créé par with. nrc6nv6s contient bien une propriété begin, as- sociée à une fonction, donc c’est cette fonction qui sera invoquée pour l’appelbegin().

k’environnement créé parwithhérite de l’environnement cou- rant. cans l’exemple suivant :

v6r 6 =

with ({b: }) { b //:

6 //:

}

ka variable b est résolue comme précédemment, en utilisant l’environnement créé par with. lais la variable 6, qui n’est pas présente dans l’objet passé à with, est bien résolue en utilisant l’environnement parent qui existe autour dewith; ici c’est l’envi- ronnement global.

conc, pour restreindre le module utilisé pour la construction d’un arbre de syntaxe, on utilisewithainsi :

num objet global plus show ... ... plus num show environnement du with créé par show() with (show({num,plus})) {

plus.new(num.new( ), num.new( )).show() //: " + "

}

k’expression construite à l’intérieur du bloc délimité parwith

fait référence simplement à num et plus. lais ici, comme nous sommes dans le corps duwith,numetplusfont référence aux ob- jets retournés par l’appel de show, pas aux objets originaux. kes originaux sont inaccessibles car masqués par les noms des déri- vés dans l’environnement du bloc. oar construction, les expressions créées à l’intérieur duwithne peuvent pas mélanger plusieurs va- riantes du langage.

tn autre aspect de cette construction est que les dérivés denum

etplusexistent uniquement dans le corps duwith; ils ne peuvent pas être référencés en dehors. kewith(show)correspond donc à une activation locale du module show, locale dans la syntaxe et dans le temps.

ka construction with n’est pas strictement nécessaire ici. nn peut très bien utiliser une fonction à la place :

1 (function({num}) {

2 num.new( ).show() //: " " 3 }(show({num,plus}))

bette fonction attend un module qui contientnumpour pouvoir créer et aǦcher l’expressionnum( ). nn boucle ce module en lui passant show() comme argument à la ligne 3, ce qui a le même eǣet quewith: le code à l’intérieur de la fonction est exécuté.

ka syntaxe étant plus lourde avec une fonction qu’avecwith , on utilisera with dans les exemples qui suivent.

bette fonction etwithont en commun de créer un environne- ment lexical pour le code de leur bloc. oour empêcher la création d’expressions mixtes, il suǦt donc de contrôler les noms qui sont