Article publié dans Linux Magazine 126, avril 2010.
Copyright © 2010 Christian Aperghis-Tramoni
Nous avons vu jusqu'à maintenant comment réaliser des programmes classiques en PIR, le chapitre que nous abordons va être consacré à l'étude des structures et à la gestion des applications orientées objet.
Les programmes peuvent être téléchargé sur mon site : http://www.dil.univ-mrs.fr/~chris/Documents/progs04.pod
La quatrième et dernière partie de la série consacrée à PIR que nous allons présenter va nous permettre de détailler les possibilités offertes par le langage en terme de programmation orientée objet [poo]. Grâce à de nombreux exemples nous allons pouvoir expliquer comment faire pour déclarer des classes, ou bien procéder à la création de nouveaux objets. Nous verrons aussi comment définir les méthodes qui leur sont attachées.
La programmation par objets est un paradigme de programmation informatique. Sa base est de définir et d'assembler de unités logicielles que l'on appelle objets.
Un objet est une structure de données correspondant à un ensemble de valeurs qui vont permettre de définir son état ainsi qu'un ensemble de messages spécifiant son comportement. Il est donc représentatif d'un concept auquel seront attachés des attributs et les méthodes permettant d'y accéder.
Les attributs définissent la structure interne alors que l'ensemble des méthodes d'accès qui décrivent comment seront traités les messages formant son interface. Ce n'est qu'à travers elles qu'il sera possible d'accéder aux données de l'objet concerné.
Toutes ces informations sont cachées, on dira encapsulées
. Le principal
avantage étant la capacité de modifier la structure interne des objets ou des
méthodes sans que ces actions aient un quelconque impact sur les usagers.
Le langage PIR est prévu pour proposer une syntaxe qui permet de simplifier l'ensemble des opérations dans le cadre d'une programmation de ce type, en particulier, en ce qui concerne les méthodes et les appels qui respectent scrupuleusement les conventions définies par la machine virtuelle [Parrot].
La classe est représentative de la structure de l'objet, c'est-à-dire la définition de l'ensemble des entités qui le composent.
Tout objet doit donc être donc issu d'une classe, on peut le considérer comme le produit brut qui sort d'un moule et qui devra donc être affiné.
Dans la réalité un objet est l'instanciation d'une classe. On pourra donc parler indifféremment d'objet ou d'instance (éventuellement d'occurrence).
Une classe est composée d'attributs et de méthodes.
Plusieurs instanciations de classes pourront avoir leurs attributs égaux sans pour autant représenter un seul et même objet, c'est ainsi qu'on fera la différence entre état et identité.
Nous avons évoqué l'encapsulation comme l'un des concepts du paradigme objet. C'est un mécanisme qui permet de rassembler l'ensemble des informations dans une structure permettant de dissimuler l'implémentation de l'objet. De cette manière, on pourra empêcher l'accès aux données par tout autre moyen que les services définis lors de sa création.
C'est l'encapsulation qui va garantir l'intégrité des données.
Un espace de noms permet de contrôler la visibilité des attributs et des méthodes définis lors de la création des objets [Noms].
Le fonctionnement est simple, il faut savoir que le nom d'une propriété ou d'une méthode est constitué de deux parties, l'identifiant et l'espace de noms lui même.
Les espaces de noms proposent un mécanisme dans lequel les noms peuvent être réutilisés. Ceci peut ne pas paraître fondamental, mais dans le cas d'un système complexe ou d'une application qui nécessite l'utilisation d'un grand nombre de bibliothèques, cet état de choses peut être extrêmement utile.
Chaque espace de noms définissant sa propre zone pour les noms de fonction et les noms de variables, il nous sera possible de disposer de plusieurs fonctions portant le même nom, d'en créer de nouvelles ou d'en convertir, d'autres sans avoir à mettre en œuvre la Multi-Method Dispatch (MMD) qui a été présentée au chapitre précédent.
Les espaces de noms sont aussi indispensables pour définir des classes dont nous parlerons plus tard.
Un espace de noms est spécifié au moyen de la directive
.namespace ["Nom_Espace"]
.
Entre les crochets qui sont obligatoires, une chaîne de caractères
identifiera l'espace en question.
La racine des espaces de noms peut être représentée sans clé interne
.namespace [ ]
. Dans ce cas, l'espace de noms sera par défaut parrot
.
Si on désire créer un espace de noms référencé, cette référence apparaîtra
comme clé lors de la déclaration .namespace ["EtreHumain"]
pour créer
un espace EtreHumain
ou bien .namespace ["Vivant" ; "Homme"]
pour
créer l'espace de noms Vivant::Homme
. Ainsi, en utilisant des points
virgule (;
) il est possible d'imbriquer les espaces de noms à une
profondeur quelconque.
L'apparition de l'instruction .namespace
sans les crochets provoquera une
erreur.
coruscant chris$ cat erreur.pir .namespace .sub "Programme en erreur." :main .local pmc nom nom = new "String" .end coruscant chris$ parrot erreur.pir error:imcc:syntax error, unexpected '\n', expecting '[' in file 'test.pir' line 1 coruscant chris$
Les espaces de noms sont des PMC d'un type spécifique. On les manipule donc exactement de la même manière que tous les autres PMC.
Il est toujours possible de récupérer le PMC de l'espace de noms situé
à la racine au moyen du code opération get_root_namespace
.
Il est aussi possible de
connaître nom de l'espace de noms courant, qui peut être différent du nom
de la racine de l'espace de noms. C'est l'instruction get_namespace
qui
réalise cette opération.
coruscant chris$ cat espace.pir .namespace [] .sub "Recuperation de l'espace de noms" :main .local pmc nom nom = new "String" nom = get_namespace print "Nous sommes dans l'espace de noms : " say nom .end coruscant chris$ parrot espace.pir Nous sommes dans l'espace de noms : parrot coruscant chris$ cat espace1.pir .namespace ["Mon_espace"] .sub "Recuperation de l'espace de noms" :main .local pmc nom nom = new "String" nom = get_namespace print "Nous sommes dans l'espace de noms : " say nom .end coruscant chris$ parrot espace.pir Nous sommes dans l'espace de noms : Mon_espace coruscant chris$
En spécifiant comme clé un nom d'espace, il est possible d'obtenir le PMC représentatif de l'espace de noms en question.
.local pmc reference reference = get_namespace ["EtreHumain"]
Une fois récupéré, un PMC espace de noms servira à retrouver les variables globales ou celles appartenant à un autre espace.
Il sera aussi possible de retrouver l'ensemble des variables globales de l'espace courant :
coruscant chris$ cat espace.pir .sub main .local pmc espace, nom espace = get_global "MonEspace" nom = espace["ma_fonction"] say nom nom() .end .namespace ["MonEspace"] .sub ma_fonction say "Dans ma_fonction de l'espace de noms." .end coruscant chris$ parrot espace.pir ma_fonction Dans ma_fonction de l'espace de noms. coruscant chris$
Si on le désire, on peut faire porter la recherche sur les variables globales d'un espace de noms spécifique.
coruscant chris$ parrot espace.pir .sub main .local pmc espace1, espace2 espace1 = get_global "ma_fonction" say espace1 set_global ["MonEspace"], "nouveau", espace1 espace2 = get_global ["MonEspace"], "nouveau" say espace2 espace2() .end .sub ma_fonction say "On est dans la fonction." .end coruscant chris$ parrot espace.pir ma_fonction ma_fonction On est dans la fonction. coruscant chris$
Ainsi que nous venons de le voir dans cet exemple, une fois trouvé à partir d'un espace de noms, un PMC sous-programme peut être référencé exactement de la même manière que n'importe quel sous-programme.
Nous venons de définir ce qu'est un espace de noms, intéressons nous maintenant à l'ensemble des possibilités qui nous sont offertes par la mise en œuvre de ces outils. Il s'agit de la programmation orientée objet.
La principale caractéristique de cette technique de programmation est la possibilité qui est donnée de créer des méthodes attachées à la structure.
Une méthode peut être assimilée à un sous-programme à la différence près que l'appel doit obligatoirement être fait par l'intermédiaire d'un PMC, lequel est passé en paramètre.
La syntaxe de base pour référencer une méthode ressemble quelque peu à celle utilisée pour invoquer un sous-programme classique.
"Nom_Objet"."nom_methode"(Liste d'arguments)
La principale différence vient du fait qu'une méthode étant rattachée à un objet particulier, il est nécessaire de spécifier en plus de son nom propre celui de l'objet auquel elle a été attachée.
Il faut aussi noter que le nom de la méthode doit être une chaîne de caractères et donc se présenter entre guillemets. Si ce n'est pas le cas, il sera traité comme un nom de variable.
.local string Nom_Methode Nom_Methode = "Ma_Methode" # Appel par nom explicite. Mon_Objet."Ma_Methode"() # Par l'intermédiaire d'une variable. Mon_Objet.Nom_Methode()
L'invoquant peut être indifféremment une variable ou un registre, et le nom de la méthode peut être un littéral chaîne de caractères, une variable chaîne de caractères ou un objet méthode PMC.
Une méthode sera définie comme n'importe quel sous-programme, à deux différences majeures près.
Elle doit se situer dans un espace de noms du nom de la classe dont elle fait partie.
Elle doit utiliser l'indicateur spécifique :method
.namespace ["Ma_Classe"] .sub "Ma_Methode" :method .param string valeur print "Valeur transmise : " say valeur .end
Nous verrons aussi plus tard sur des exemples que, à l'intérieur du corps de la
méthode, l'objet invoquant peut être consulté au moyen du mot clé self
.
Si on le désire, il est aussi possible de définir un nouveau nom pour l'objet
qui vient d'être invoqué au moyen de la directive :invoquant
.
Dans l'exemple qui suit nous définissons deux méthodes rattachées à la classe
Mon_Espace
. Il est fait référence à la première, Dire_Bonjour
, à partir
du module principal de l'unité de compilation, alors que l'autre,
Dire_Aurevoir
sera appelée à partir du corps de la première méthode,
et ce, de deux manières différentes.
Le premier appel se fera en utilisant la chaîne de caractères spécifiant
explicitement son nom (self."Dire_Aurevoir"()
), l'autre par
l'intermédiaire d'un registre dans lequel le nom aura été stocké
($S0 = "Dire_Aurevoir"
) puis self.$S0()
).
coruscant chris$ cat methode.pir .sub main .local pmc classe .local pmc objet # On crée une nouvelle classe a partir de l'espace de noms Mon_Espace. newclass classe, "Mon_Espace" # Instantiation d'un objet de la classe Mon_Espace new objet, "Mon_Espace" # Test de l'existence d'une methode. $I0 = can objet, "Dire_Bonjour" print "Test de la methode Dire_Bonjour : " say $I0 $I0 = can objet, "Dire_Adieu" print "Test de la methode Dire_Adieu : " say $I0 # Appel de la methode Dire_Bonjour dans l'espace de noms Mon_Espace. objet."Dire_Bonjour"() say "Fin du programme." end .end # Initialisation de l'espace de noms Mon_Espace .namespace ["Mon_Espace"] # Définition de la methode globale Mon_Espace::Dire_Bonjour .sub Dire_Bonjour :method say "Bonjour." # Invoquation de Mon_Espace::Dire_Aurevoir self."Dire_Aurevoir"() # Positionnement du nom de la methode dans un registre. $S0 = "Dire_Aurevoir" self.$S0() .end # Définition de la methode globale Mon_Espace::Dire_Au_Revoir .sub Dire_Aurevoir :method say "Au Revoir." .end coruscant chris$ parrot methode.pir Test de la methode Dire_Bonjour : 1 Test de la methode Dire_Adieu : 0 Bonjour. Au Revoir. Au Revoir. Fin du programme. coruscant chris$
Pour chaque appel de méthode le nom considéré apparaît dans l'espace de l'objet correspondant à la classe concernée.
La directive .sub
crée automatiquement une entrée dans la table de symboles
de l'espace de noms courant pour le sous-programme en question, mais lorsqu'une
fonction .sub
est étiquetée en tant que :method
elle définit automatiquement
une variable locale qui s'appelle self
et lui assigne un objet passé en
tant que paramètre.
Il n'est donc pas nécessaire de déclarer de manière explicite
.param pmc self
pour la récupérer, cette directive vient toute seule avec
la définition :method
.
Il est aussi possible de passer plusieurs arguments à une méthode et de récupérer plusieurs valeurs de retour, exactement comme un appel de sous-programme.
(Res1, Res2) = "Mon_Objet"."Ma_methode" (Arg1, Arg2)
Nous avons rapidement défini dans la première partie la notion de v-table. Nous allons y revenir ici de manière plus détaillée en développant quelques applications.
Tous les PMC souscrivent à une interface commune appelée v-table. C'est cette dernière activée lors de la création du PMC qui va lui permettre de réaliser toutes les tâches standard de bas niveau qui lui sont spécifiques.
Le terme générique de v-table est l'abréviation de Virtual Function Table qui représente une structure destinée à contenir l'ensemble des éléments et des références qui la caractérise.
Les interfaces de la v-table sont, à de nombreux égards, semblables aux fonctions et aux méthodes que l'on a l'habitude de manipuler. Dans la littérature, elles sont généralement appelées fonctions de la v-table (Vtable functions), méthodes de la v-table (Vtable methods) ou bien encore entrées de la v-table (Vtable entries).
En fait, ces interfaces ne sont en aucun cas des sous-programmes ou des méthodes au sens ou on l'entend généralement.
À l'instar des méthodes relatives à un objet, les interfaces de la v-table
sont définies pour une classe spécifique de PMC et peuvent être invoquées
sur n'importe quel membre créé à partir de cette classe. De la même manière,
dans la définition d'une v-table on va utiliser le mot clé self
pour
décrire l'objet invoqué.
C'est à ce niveau que vont s'arrêter les similitudes, car contrairement aux sous-programmes ordinaires, il n'est en aucun cas possible d'invoquer directement une méthode attachée à une v-table et elles n'héritent pas non plus d'une hiérarchie de classes comme le font les méthodes classiques.
Maintenant que nous avons défini la terminologie, nous allons pouvoir décrire de manière détaillée ce qu'est une v-table et de quelle manière elle sera utilisée dans le cadre de la machine virtuelle.
L'interface v-table est le seul et unique moyen pour accéder aux données du PMC si on désire les consulter ou de les modifier, ce sera aussi aussi de cette manière que sera invoqué le PMC si ce dernier est représentatif d'un sous programme. Ils ne sont jamais appelés directement à partir du code PIR mais de manière interne par la machine virtuelle en faisant référence à des codes opérations spéciaux ou en fonction de comportements appropriés.
Par exemple, le code opération invoque
permet de faire référence à la
v-table spécifique d'un PMC alors que le code opération inc
appelle
la méthode appropriée effectuant un incrément sur la valeur de l'objet
défini par le PMC de référence en fonction du type représenté.
En définitive, cette interface permet au programmeur de faire évoluer la manière même dont la machine virtuelle accède aux données, et même de modifier le comportement des codes opérations, on dit surcharger, vis à vis ces données.
Il est en effet possible de définir (de surcharger) l'interface d'une v-table
directement dans un programme PIR en utilisant l'indicateur :vtable
dans
une déclaration de sous programme.
Cette technique sera utilisée pour sous-classer un PMC existant dans du code PIR afin de créer un nouveau type de données comportant des méthodes d'accès personnalisées.
.sub "set_integer" :vtable # Positionne la valeur entière du PMC .end
Ici, le sous-programme invoqué doit conserver le nom défini dans l'interface de la v-table qu'il est destiné à mettre en œuvre. Toutes les interfaces de la v-table ont des noms spécifiques et il est interdit d'en changer une en lui donnant un nom arbitraire.
Cependant, si on désire renommer la fonction avec un nom différent, tout en continuant à l'utiliser comme une interface de la v-table, il est possible d'ajouter un paramètre à l'indicateur.
.sub "MySetInteger" :vtable("set_integer") # Positionne la valeur entière du PMC .end
Les interfaces de la v-table proposent aussi souvent l'indicateur :method
afin de pouvoir les utiliser directement dans du code PIR comme des méthodes
en plus d'être utilisées comme des v-tables par la machine virtuelle.
C'est ainsi que nous pouvons trouver :
.namespace ["MaClasse"] .sub 'Chaine' :vtable("get_string") :method $S0 = "Bonjour!" .return($S0) .end .local pmc classe = new "MaClasse" say classe # Affiche Le nom de la classe. $S0 = classe # Stocke Bonjour dans $S0. $S0 = classe.'Chaine'() # Stocke Bonjour dans $S0.
Nous allons maintenant appliquer tout ceci en écrivant un programme.
coruscant chris$ cat vtable.pir .sub main :main .local pmc classe classe = newclass "MaClasse" print "Nom de la classe : " say classe $P1 = new classe $P2 = $P1 print "Premier appel. Contenu du registre : " say $P2 $P2 = $P1."Chaine"() print "Second appel. Contenu du registre : " say $P2 .end .namespace ["MaClasse"] .sub "Chaine" :vtable("get_string") :method $P0 = new "String" $P0 = "Bonjour !" .return($P0) .end coruscant chris$ parrot vtable.pir Nom de la classe : MaClasse Premier appel. Contenu du registre : Bonjour ! Second appel. Contenu du registre : Bonjour ! coruscant chris$
Lors du premier appel, le nom de la classe créée a été stocké dans un
PMC ($P1
). Faire référence au PMC en question ($P2 = $P1
) active
la méthode qui lui est attachée et renvoie la chaîne de caractères.
Mais il est aussi possible de préciser le nom complet de la méthode
$P2 = $P1."Chaine"()
.
Bien que PIR n'utilise pas à proprement parler la syntaxe traditionnelle des langages orientés objet, il offre toutes les fonctionnalités indispensables pour les gérer presque aussi bien que les langages spécialisés.
Les PMC ne sont pas des classes au sens où on l'entend généralement, mais des structures polymorphes qui peuvent couvrir un grand nombre de types prédéfinis. Nous avons vu, au chapitre précédent comment on les utilise pour travailler sur les agrégats (listes, hash).
Comme les PMC ont une interface standard, la v-table qui représente la liste des fonctions que tout PMC peut mettre en œuvre, un PMC donné peut, au choix, implémenter explicitement la fonction correspondante ou choisir de laisser Parrot mettre en place la fonction par défaut.
Il est toujours possible de définir des sous-classes de PMC existants pour associer de nouvelles informations ou de nouvelles méthodes au PMC de base.
La création d'une nouvelle sous-classe à partir d'une classe de base se fait
fait en utilisant le mot clé subclass
.
Cette opération va nous permettre de récupérer une classe PMC qui pourra être utilisée pour créer de nouvelles classes, apporter des modifications en ajoutant des attributs et des méthodes et créer des objets dépendant de cette classe.
Dans l'exemple qui suit, nous allons créer les sous-classes MaChaine
et MaSousChaine
, définies à partir de la classe existante String
à laquelle a été rattachée une nouvelle méthode visualise
.
coruscant chris$ cat classes.pir .sub main :main $P0 = new "String" $P0.'visualise'("Appel direct de la classe 'String'.") # Creation d'une sous classe de la classe "String". $P2 = get_class "String" $P1 = subclass $P2, "MaChaine" # Creation d'une sous classe par reference. $P3 = new $P1 $P3.'visualise'("Appel 1 de la sous classe 'MaChaine'" ) # Creation d'une sous classe par nom. $P4 = new "MaChaine" $P4.'visualise'("Appel 2 de la sous classe 'MaChaine'") .local pmc nomclasse # Creation d'une sous classe a partir de la sous classe "MaChaine" $P2 = get_class "MaChaine" $P5 = subclass $P2, "MaSousChaine" nomclasse = new $P5 nomclasse.'visualise'("Appel de la sous classe 'MaSousChaine'") .end .namespace ["String"] .sub "visualise" :method .param string x print "Message : " say x .end coruscant chris$ parrot classes.pir Message : Appel direct de la classe 'String'. Message : Appel 1 de la sous classe 'MaChaine' Message : Appel 2 de la sous classe 'MaChaine' Message : Appel de la sous classe 'MaSousChaine' coruscant chris$
Il est évident dans cet exemple que les sous-classes héritent toutes de la méthode qui a été ajoutée à la classe de base.
La machine virtuelle a, de manière native, la capacité de créer et de manipuler des objets. Initialement, l'objectif de Parrot a été l'implémentation de Perl6, qui possède toutes les fonctionnalités de la programmation orientée objet [Rakudo]. Cet objectif ayant été étendu, plusieurs autres langages sont susceptibles de voir leur installation concrétisée sur la plate-forme en question [Langages]. Parrot doit, de ce fait, être capable de fournir un support utilisable par eux (Python, Ruby, PHP, JavaScript, Lua, etc) lesquels sont eux aussi majoritairement orientés objet [Langages].
Nous avons rapidement vu comment utiliser des classes existantes (String
)
pour les enrichir ou pour leur rattacher des sous-classes. Nous allons
voir maintenant
comment créer ses propres classes et instancier des objets.
Pour y parvenir, nous devons revenir sur la notion d'espace de noms.
C'est cette construction qui va nous permettre de regrouper un ensemble de fonctionnalités dans une entité unique. De cette manière, plusieurs sous-routines peuvent avoir un nom identique à condition d'appartenir à des espaces de noms différents.
Par exemple, il sera possible de positionner toutes les routines traitant
des individus sans l'espace de noms Personnes
et toutes celles
traitant de la programmation dans un espace de noms différent, Processus
dans chacun des deux espaces de noms nous pouvons alors créer une fonction
identifie()
ayant chacune ses caractéristiques propres.
.namespace ["Personne"] .sub identifie :method say "Dans l'espace de noms Personne." .end .namespace ["Processus"] .sub identifie :method say "Dans l'espace de noms Processus."
La directive .namespace
nous permet de spécifier le nom d'espace
dans lequel vont se regrouper les méthodes attachées à l'espace.
On fera appel à la méthode
identifie
de l'espace de noms Personne
sous la forme
"Personne"."identifie"()
et à celle de l'espace de noms Processus
"Processus"."identifie"()
.
Un espace de noms se termine dès l'apparition d'une nouvelle directive
.namespace
qui va changer le nom d'espace ou bien lorsque la fin du
programme est atteinte (.end
).
Il faut noter que les déclarations d'espaces de noms en PIR sont,
à la syntaxe près, très similaires à la déclaration newclass
de Perl.
Il existe cependant quelques différences sur lesquelles nous allons revenir.
En PIR, la création de classes est quelque chose de relativement facile grâce aux opérateurs spécifiques intégrés au langage.
Définissons pour commencer la directive newcass
qui va nous permettre,
comme son nom l'indique, de créer une nouvelle classe.
Elle se présente sous la forme :
$P0 = newclass "Nom_de_la_classe"
$P0
est un registre PMC.
"Nom_de_la_classe"
est le nom qui sera donné à la classe que
l'on désire créer.
Il est alors indispensable d'instancier les objets qui vont dépendre à la classe qui vient d'être définie. Cette opération ne présente pas beaucoup plus de difficultés :
Mon_Objet = new "Nom_de_la_classe"
Mon_Objet est un PMC, registre explicite ou nom de variable de type PMC.
"Nom_de_la_classe"
est le nom de la classe que l'on vient de créer
au moyen de la directive newclass
.
En reprenant l'exemple qui a été évoqué plus haut, nous pouvons procéder aux déclarations suivantes :
.sub "main" :main .local pmc Personne .local pmc Processus .local pmc Ref_Personne, Ref_Processus Ref_Personne = newclass "Personne" Ref_Processus = newclass "Processus" $P10 = new "Personne" $P11 = new "Processus" .end
La valeur de retour générée par la directive newclass
nous sera très utile
lors de développements ultérieurs.
Il nous reste, pour que le programme soit complet, à déclarer les deux
espaces de nom Personne
et Processus
.
C'est ainsi que nous obtenons le code opérationnel suivant :
coruscant chris$ cat objets.pir .sub "main" :main .local pmc Personne .local pmc Processus .local pmc Ref_Personne, Ref_Processus Ref_Personne = newclass "Personne" Ref_Processus = newclass "Processus" .end .namespace ["Personne"] .sub individu .end .namespace ["Processus"] .sub programme .end coruscant chris$ parrot objets.pir coruscant chris$
Si son exécution ne génère aucune erreur, nous n'obtenons aucun résultat car il n'y a rien à afficher.
Nous allons maintenant attacher des méthodes aux objets que nous savons créer.
Il a déjà été précisé que pour distinguer une méthode d'un sous-programme,
on dispose dans PIR d'un marqueur syntaxique, le modificateur
:method
qui, ajouté après l'identification du code lors de la déclaration
de la routine, met en évidence cet état de fait.
Dans l'exemple qui suit, la directive .namespace
nous permet de créer
une classe EtreHumain
.
Dans ce nouvel espace de noms définissons une méthode identite
qui récupère
deux paramètres.
L'instruction .sub ident :method
nous permet de réaliser cette opération.
Dès lors, toutes les sous-classes crées à partir de la classe
EtreHumain
vont hériter de la méthode qui vient de lui être attachée.
Nous créons maintenant trois références $P10
, $P11
et $P12
à des
sous classes Homme
, Femme
et Enfant
dérivées de la classe
EtreHumain
Il nous est maintenant possible de créer autant de nouveaux objets appartenant aux sous-classes en question, tous héritant de l'environnement de la classe d'origine.
coruscant chris$ cat etrehumain.pir .sub _ :main $P0 = newclass "EtreHumain" $P1 = get_class "EtreHumain" $P10 = subclass $P1, "Homme" $P11 = subclass $P1, "Femme" $P12 = subclass $P1, "Enfant" .local pmc Jean Jean = new $P10 .local pmc Julie Julie = new $P11 .local pmc Olivier Olivier = new $P12 .local pmc Claude Claude = new $P12 .local pmc Marie Marie = new $P12 Jean.'identite'("Jean", "homme") Julie.'identite'("Julie", "femme") Marie.'identite'("Marie", "enfant") Olivier.'identite'("Olivier", "enfant") Claude.'identite'("Claude", "enfant") .end .namespace ["EtreHumain"] .sub identite :method .param string nom .param string sexe print "Je suis un(e) " print sexe print " et je m'appelle " say nom .end coruscant chris$ parrot etrehumain.pir Je suis un(e) homme et je m'appelle Jean Je suis un(e) femme et je m'appelle Julie Je suis un(e) enfant et je m'appelle Marie Je suis un(e) enfant et je m'appelle Olivier Je suis un(e) enfant et je m'appelle Claude coruscant chris$
Dans un premier temps, la classe EtreHumain
a été
déclarée $P0 = newclass "EtreHumain"
et une méthode (identite
)
a été créée dans l'espace de noms correspondant .sub identite :method
cette méthode récupère deux paramètres nom
et sexe
.
Par la suite, une fois les sous-classes définies c'est la directive
new
qui va permettre l'instanciation effective d'objets.
À ce propos, nous avons déjà noté que le nom de la méthode se
présente entre quotes. Rappelons que ceci est dû au fait que, si les
quotes étaient absentes, l'identificateur serait considéré comme un nom
de variable qui aurait dû être déclaré comme un symbole local .local
.
C'est ainsi que, au lieu d'écrire
Jean."identite"("Jean", "homme")
On aurait pu écrire
.local string MonIdentite MonIdentite = "identite" Jean.MonIdentite ("Jean", "homme")
Voyons un autre exemple dans lequel l'appel de la méthode se fait par l'intermédiaire d'une variable.
coruscant chris$ cat methode.pir .namespace ["Message"] .sub Bonjour :method say "Bonjour a tous." .end .sub Aurevoir :method say "Au Revoir." .end .namespace [] .sub "Appel de Methodes" :main .local pmc MaClasse MaClasse = newclass "Message" .local pmc Annonce Annonce = new "Message" .local string methode methode = "Bonjour" Annonce.methode() methode = "Aurevoir" Annonce.methode() .end coruscant chris$ parrot methode.pir Bonjour a tous. Au Revoir. coruscant chris$
La variable methode
de type String
contient la chaîne de caractères
représentative du nom de la méthode que l'on souhaite activer.
Nous nous sommes contentés jusqu'à présent de créer des classes et de leur associer des méthodes. Nous n'avons pas encore vu comment associer des caractéristiques spécifiques aux nouvelles classes que nous construisons.
C'est par l'intermédiaire du PMC qui est positionné lors de l'exécution de
la directive newclass
que ces opérations pourront être réalisées.
$P0 = newclass "Nom_de_la_classe" .local pmc MaClasse MaClasse = newclass "Nom_de_la_classe"
Jusqu'à présent, nous n'avons pas encore utilisé cette information. Le PMC en question contient le descripteur qui va permettre de référencer la classe et d'en décrire les attributs, et ce sont les données qui vont permettre de la caractériser.
Les instructions permettant de manipuler les attributs sont :
addattribute
pour définir une nouvelle caractéristique.
setattribute
pour y stocker une valeur spécifique.
getattribute
pour récupérer la valeur préalablement stockée.
La seule contrainte pour pouvoir effectuer ces opérations est que toute référence aux valeurs de ces attributs doit impérativement se faire par l'intermédiaire d'un PMC.
coruscant chris$ cat etrehumain.pir .sub _ :main $P0 = newclass "EtreHumain" # Attributs de l'etre humain, nom et sexe. addattribute $P0, "Sexe" addattribute $P0, "Nom" .local pmc etre_humain etre_humain = get_class "EtreHumain" .local pmc homme .local pmc femme homme = subclass etre_humain, "Homme" femme = subclass etre_humain, "Femme" .local pmc Jean # Creation de l'objet "Jean" Jean = new homme .local pmc sexe sexe = new "String" sexe = "Masculin" .local pmc nom nom = new "String" nom = "Jean" # Sexe : "Masculin" setattribute Jean, "Sexe", sexe # Nom : "Jean" setattribute Jean, "Nom", nom .local pmc Julie # Creation de l'objet "Julie" Julie = new femme .local pmc sexe sexe = new "String" sexe = "Feminin" .local pmc nom nom = new "String" nom = "Julie" # Sexe : "Feminin" setattribute Julie, "Sexe", sexe # Nom : "Julie" setattribute Julie, "Nom", nom .local pmc Christian # Creation de l'objet "Christian" Christian = new homme .local pmc sexe sexe = new "String" sexe = "Masculin" .local pmc nom nom = new "String" nom = "Christian" # Sexe : "Masculin" setattribute Christian, "Sexe", sexe # Nom : "Christian" setattribute Christian, "Nom", nom # Appel des methodes attachees aux objets. Jean.'identite'("Bonjour") Christian.'identite'("Salut") Julie.'identite'("Hello") .end .namespace ["EtreHumain"] .sub identite :method .param string message .local pmc sexe sexe = new "String" sexe = getattribute self, "Sexe" .local pmc nom nom = new "String" nom = getattribute self, "Nom" print message print " je m'appelle " print nom print " et je suis de sexe " say sexe .end coruscant chris$ parrot etrehumain.pir Bonjour je m'appelle Jean et je suis de sexe Masculin Salut je m'appelle Christian et je suis de sexe Masculin Hello je m'appelle Julie et je suis de sexe Feminin coruscant chris$
Étudions en détail le contenu du programme que nous venons d'écrire.
Pour commencer, on crée la classe appelée EtreHumain
, cette opération
renvoie un PMC qui contient le descripteur de cette nouvelle classe.
Ce descripteur sera alors utilisé pour ajouter deux attributs à cette classe.
Le premier Nom
sera utilisé pour stocker le nom de la personne et le second
Sexe
son sexe.
C'est ici qu'on voit l'avantage de l'étiquetage d'un sous-programme en tant
que méthode, ce sera, pour nous, le moyen de récupérer un PMC appelé
self
qui référence l'objet sur lequel agit le code.
C'est cette facilité qui est utilisée par la méthode identite
pour récupérer le contenu des attributs Nom
et Sexe
à partir du
PMC self
afin de les afficher.
La situation est donc la suivante :
On a créé un espace de noms EtreHumain
auquel a été rattachée une méthode
identite
qui récupère un paramètre dans sa liste d'appel et accède aux
attributs Nom
et Sexe
de l'objet.
On a ensuite défini dans l'espace de noms que l'on vient de créer deux
sous-classes Homme
et Femme
dont les références seront respectivement
contenues dans les PMC homme
et femme
qui vont hériter des
caractéristiques de l'espace de noms EtreHumain
, à savoir la méthode
identite
et les deux attributs Nom
et Sexe
.
Tout ce qui nous reste à faire maintenant est de créer de nouveaux humains et de positionner leurs caractéristiques.
Nous avons évoqué la contrainte de devoir utiliser un PMC pour affecter une
valeur aux caractéristiques. C'est la raison d'être des déclarations des PMC
de type String
lors de la création des nouveaux objets.
.local pmc sexe sexe = new "String" sexe = "****" .local pmc nom nom = new "String" nom = "****" setattribute ****, "Sexe", sexe setattribute ****, "Nom", nom
Une fois instanciés, les PMC sont transmis par l'intermédiaire de
l'instruction setattribute
.
Lors de la création d'un objet, il est possible de déclarer dans l'espace de
nom des méthodes spécifiques. L'une d'elle __init
sera automatiquement
exécutée à chaque nouvelle instanciation. Elle pourra servir, par exemple,
pour procéder à l'initialisation de certains attributs.
coruscant chris$ cat initialisation.pir .sub main :main .local pmc perroquet, ara, parrot perroquet = newclass 'Conure' addattribute perroquet, "Nom" ara = new ["Conure"] $P2 = getattribute ara, "Nom" say $P2 parrot = new ["Conure"] $P2 = getattribute parrot, "Nom" say $P2 .end .namespace ["Conure"] .sub __init :method $S0 = typeof self $P0 = new ["String"] .local pmc STDIN STDIN = getstdin print "Vous creez un nouveau " print $S0 print ". Quel est son nom ? " $S0 = readline STDIN chopn $S0, 1 $P0 = $S0 setattribute self, "Nom", $P0 .end coruscant chris$ parrot initialisation.pir Vous creez un nouveau Conure. Quel est son nom ? Cacatoes Cacatoes Vous creez un nouveau Conure. Quel est son nom ? Half Moon Half Moon coruscant chris$
De nouveau, à l'intérieur de la définition de l'interface de la v-table,
la variable locale self
contient le PMC sur lequel l'interface de la
v-table est invoqué, exactement comme dans la déclaration d'une méthode.
Le code opération subclass
permet à une sous-classe
d'hériter des attributs et des méthodes et des méthodes d'une classe de base.
Créons maintenant une classe de base Jungle
a laquelle seront rattachés les
quatre caractéristiques et les quatre méthodes qui seront communes à tous les
êtres de la forêt.
Nous pouvons alors déclarer des sous-classes des habitants de la jungle,
les fauves, les reptiles et les conures. Chacune de ces sous-classes hérite
des caractéristiques et des méthodes de la classe Jungle
.
Pour faciliter les déclarations et l'édition des résultats, nous créons deux macro-instructions.
La première CreerEntree
permet de créer une nouvelle entrée dans la
classe Jungle
en positionnant les divers attributs qui lui sont transmis
par l'intermédiaire de la liste d'appel et d'en retourner la référence
dans un PMC.
La seconde va tout simplement éditer les données d'un objet de la
classe. Elle n'a besoin que de connaître la référence de l'objet
pour en extraire toutes les caractéristiques.
coruscant chris$ cat jungle.pir .macro CreerEntree (Ref, Famille, Espece, Nom, Sexe, Naissance) .local pmc .Ref .Ref = new .Famille $P0 = new "String" $P0 = .Espece setattribute .Ref, "Espece", $P0 $P0 = new "String" $P0 = .Nom setattribute .Ref, "Nom", $P0 $P0 = new "String" $P0 = .Sexe setattribute .Ref, "Sexe", $P0 $P0 = new "Integer" $P0 = .Naissance setattribute .Ref, "Naiss", $P0 .endm .macro Edite (Ref) $S1 = .Ref.'Espece'() print " Je suis un(e) " say $S1 $S1 = .Ref.'Nom'() print " Je m'appelle " say $S1 $S1 = .Ref.'Sexe'() print " Je suis de sexe " say $S1 $I1 = .Ref.'Age'() print " Je suis ne(e) en " print $I1 $I0 = time $S0 = localtime $I0 $S0 = substr $S0, -5, 4 $I0 = $S0 $I0 -= $I1 print " et j'ai " print $I0 say " ans.\n" .endm .namespace ["Jungle"] .sub Espece :method $P0 = new "String" $P0 = getattribute self, "Espece" .return ($P0) .end .sub Nom :method $P0 = new "String" $P0 = getattribute self, "Nom" .return ($P0) .end .sub Sexe :method $P0 = new "String" $P0 = getattribute self, "Sexe" .return ($P0) .end .sub Age :method $P0 = new "Integer" $P0 = getattribute self, "Naiss" .return ($P0) $I0 = time .end .namespace [] .sub _ :main $P0 = newclass "Jungle" addattribute $P0, "Espece" addattribute $P0, "Nom" addattribute $P0, "Sexe" addattribute $P0, "Naiss" $P0 = subclass "Jungle", "Fauve" $P1 = subclass "Jungle", "Conure" $P2 = subclass "Jungle", "Reptile" .local pmc bag .CreerEntree (bag, "Fauve", "Panthere Noire", "Bagheera", "Femelle", 1967) .local pmc parrot .CreerEntree (parrot, "Conure", "Perroquet", "Half Moon", "Male", 2008) .local pmc kaa .CreerEntree (kaa, "Reptile", "Boa", "Kaa", "Male", 2000) .local pmc shere .CreerEntree (shere, "Fauve", "Tigre", "Shere Khan", "Male", 1998) say "Edition des fauves." .Edite (bag) .Edite (shere) say "Edition des conures." .Edite (parrot) say "Edition des reptiles." .Edite (kaa) .end coruscant chris$ parrot jungle.pir Edition des fauves. Je suis un(e) Panthere Noire Je m'appelle Bagheera Je suis de sexe Femelle Je suis ne(e) en 1967 et j'ai 42 ans. Je suis un(e) Tigre Je m'appelle Shere Khan Je suis de sexe Male Je suis ne(e) en 2000 et j'ai 11 ans. Edition des conures. Je suis un(e) Perroquet Je m'appelle Half Moon Je suis de sexe Male Je suis ne(e) en 2008 et j'ai 1 ans. Edition des reptiles. Je suis un(e) Boa Je m'appelle Kaa Je suis de sexe Male Je suis ne(e) en 2000 et j'ai 9 ans. coruscant chris$
Il est possible une fois les objets créée de mémoriser leur référence dans une structure. C'est ce que nous allons voir dans le programme qui suit.
coruscant chris$ cat repertoire.pir .macro Enregistre (Ref, Ag, Nom, Prof, Nais, Mail) .Ref = new "Identite" .Ref.'Remplis'(.Prof, .Nais, .Mail) .Ag[.Nom] = .Ref .endm
.namespace ["Identite"] .sub Remplis :method .param string prof .param int nais .param string mail $P0 = new 'String' $P0 = prof setattribute self, "Profession", $P0 $P0 = new 'Integer' $P0 = nais setattribute self, "Naissance", $P0 $P0 = new 'String' $P0 = mail setattribute self, "Mail", $P0 .end
.sub Edite :method .param string nom print "Nom : " say nom $P0 = getattribute self, "Profession" print "Profession : " say $P0 $P0 = getattribute self, "Naissance" print "Date de naissance : " say $P0 $P0 = getattribute self, "Mail" print "Mail : " say $P0 .end .namespace [] .sub _ :main .local pmc Repertoire Repertoire = newclass "Identite" addattribute Repertoire, "Profession" addattribute Repertoire, "Naissance" addattribute Repertoire, "Mail" .local pmc Agenda Agenda = new "Hash" .local pmc chris $S0 = "Christian Aperghis" $S1 = "Enseignant" $I0 = 1947 $S2 = "chris@monmail.fr" .Enregistre(chris, Agenda, $S0, $S1, $I0, $S2) .local pmc seb $S0 = "Sebastien Aperghis" $S1 = "Ingenieur" $I0 = 1977 $S2 = "sebastien@mommail.fr" .Enregistre(seb, Agenda, $S0, $S1, $I0, $S2) .local pmc cles cles = new 'Iterator', Agenda EXPLORE: unless cles goto FIN $S0 = shift cles $P0 = Agenda[$S0] $P0.'Edite'($S0) print "\n" goto EXPLORE FIN: .end coruscant chris$ parrot repertoire.pir Nom : Christian Aperghis Profession : Enseignant Date de naissance : 1947 Mail : chris@monmail.fr Nom : Sebastien Aperghis Profession : Ingenieur Date de naissance : 1977 Mail : sebastien@monmail.fr coruscant chris$
Les références des divers objets qui ont été créés sont stockés dans un hash. La clé d'entrée est le nom de la personne, la valeur contient la référence de l'objet correspondant qui mémorise les informations relatives au nom en question.
Ici aussi, une macro instruction nous permet de procéder à la création du hash.
On dispose dans PIR d'un mécanisme permettant de gérer au mieux les exceptions qui peuvent se présenter lors de l'exécution d'un programme.
Une exception est un événement inattendu, généralement provoqué par une erreur dans le code. Il sera en fait géré comme un objet.
Les exceptions peuvent être capturées par des gestionnaires spécifiques,
que l'on appelle handlers
.
C'est ce mécanisme permet à un programme de tenter de récupérer au mieux de ses intérêts l'erreur qui est apparue au lieu de provoquer un crash système.
Comme tous les objets utilisés dans Parrot, les exceptions sont gérées par l'intermédiaire des registres PMC.
Ils vont valider l'accès à un certain nombre d'indicateurs binaires permettant de déterminer le type et l'emplacement de l'erreur et le programme qui l'a provoquée.
De nombreuses exceptions sont utilisées de manière interne dans la machine virtuelle pour spécifier les conditions d'erreur.
Les codes opération die
et warn
par exemple, permettent de générer
une exception interne qui sera récupérée par le programme. Mais
une opération arithmétique telle que div
peut, elle aussi, en générer
une dans le cas où le diviseur serait égal à zéro.
Par ailleurs, on dispose d'une instruction throw
qui permet d'en créer
artificiellement.
Voici un exemple très simple de code qui génère une exception en utilisant
l'instruction throw
.
$P0 = new "Exception" throw $P0
Si un gestionnaire d'exception est disponible dans le code courant, alors le contrôle lui sera passé et elle sera prise en compte. Dans le cas contraire, le programme se termine.
Les exceptions sont en définitive des objets gérés par un PMC. En tant que tel elles peuvent donc avoir un certain nombre de caractéristiques utiles.
On peut, par exemple, passer le contrôle à un morceau de code qui se contentera d'afficher un message. Dans un premier temps, le mécanisme peut se présenter comme suit :
coruscant chris$ cat exception.pir .sub _ :main # Gestionnaire de l'exception. $P0 = new "Exception" $P1 = new "String" $P1 = "Ceci est le message d'erreur de l'exception." $P0["message"] = $P1 say "Avant exception" # Declenchement de l'exception. throw $P0 say "Apres exception" .end coruscant chris$ parrot exception.pir Avant exception Ceci est le message d'erreur de l'exception. current instr.: '_' pc 15 (test.pir:9) coruscant chris$
On voit bien apparaître le premier message envoyé par le programme avant la
génération de l'exception throw $P0
puis, le message d'erreur généré par
l'exception elle-même et l'indication que le programme s'est terminé sur
l'instruction 9 current instr.: '_' pc 15 (test.pir:9)
.
Par contre, comme l'exception termine le programme,
le message Apres l'exception
ne sera jamais affiché.
Autre attribut disponible, la gravité et le type de l'exception.
$P0 ["severity"] = 10 # Une valeur entière. $P0 ["type"] = 5 # Une valeur entière.
Il y a aussi la possibilité de créer n'importe quelle caractéristique additionnelle
$P0 ["Additionnelle"] = $P2 # Une référence à un PMC.
Dans ces conditions, il est nécessaire de récupérer les caractéristiques de l'exception dans un gestionnaire comme nous allons le voir.
Ce sont les codes opération push_eh
et pop_eh
qui
manipulent les gestionnaires d'exception.
Tout contexte dispose d'une liste de gestionnaires répertoriés.
L'instruction push_eh
ajoute un nouveau gestionnaire à la fin de
la liste alors que l'instruction pop_eh
en retire un.
Bien que le choix des noms choisis push
et pop
évoquent une
structure de pile, il n'en est rien.
La création d'une structure standard de gestion d'exception est la suivante :
EXCEPTION: push_eh CAPTURE * * * goto FIN CAPTURE: .get_results ($P0) FIN: pop_eh
Dans les lignes que nous venons d'écrire, l'étiquette EXCEPTION
permet
de spécifier le début du code qui gère l'exception.
Dans ce bloc, on insère le label
CAPTURE
au moyen de l'instruction (push_eh CAPTURE
), représentant
l'adresse du code qui gère l'exception.
C'est dans ce bloc, qu'il est possible de récupérer des paramètres envoyés
par le gestionnaire (.get_results ($P0)
), et lorsque la procédure se
termine, on retire le gestionnaire de la liste pop_eh
.
Comme tous les autres types de données, le gestionnaire d'exception est
référencé par un PMC. C'est le fait d'exécuter l'instruction
push_eh LABEL
qui va automatiquement créer le PMC gestionnaire.
Si on le désire, il est aussi possible de procéder à une création explicite.
$P0 = new "ExceptionHandler" set_addr $P0, GESTIONNAIRE push_eh $P0 . . . GESTIONNAIRE:
L'objet PMC qui a géré l'exception peut être récupéré dans le gestionnaire
au moyen de l'instruction .get_results ()
GESTIONNAIRE: .local pmc err .get_results (err) . . .
Il est possible de consulter et d'analyser l'ensemble des attributs afin d'obtenir l'ensemble des informations sur le problème qui a généré l'exception.
coruscant chris$ cat exception.pir .sub _ :main # Gestionnaire de l'exception. $P0 = new "Exception" $P1 = new "String" $P2 = new "Integer" $P2 = 100 $P1 = "Traitement de l'exception." $P0["message"] = $P1 $P0 ["severity"] = 100 $P0 ["type"] = 5 push_eh GESTIONNAIRE say "Avant exception" # Declenchement de l'exception. throw $P0 say "Apres exception" GESTIONNAIRE: .get_results($P0) pop_eh print "Type de l'exception : " $I1 = $P0["type"] say $I1 print "Message de l'exception : " $S1 = $P0["message"] say $S1 print "Severite de l'exception : " $I1 = $P0["severity"] say $I1 .end coruscant chris$ parrot exception.pir Avant exception Type de l'exception : 5 Message de l'exception : Traitement de l'exception. Severite de l'exception : 100 coruscant chris$
Comme tous les handlers
déclarés ne sont pas destinés à gérer toutes les
exceptions, si on se trouve dans l'impossibilité de le faire un régisseur
peut rediriger le problème vers un autre gestionnaire de la liste.
Dans ces conditions, les exceptions vont se propager à travers les éléments de la liste jusqu'à trouver le gestionnaire par défaut qui permet la sortie du programme.
coruscant chris$ cat exception.pir .sub main :main say "Debut du programme." push_eh GESTIONNAIRE $P1 = new ['Exception'] $P2 = new ['String'] set $P2, "Grosse faute." setattribute $P1, 'message', $P2 throw $P1 say "Cette ligne ne sera jamais affichee." end GESTIONNAIRE: .local pmc mess .get_results (mess) say "Gestion de l'exception." typeof $S1, mess print "Le parametre transmis est de type : " say $S1 print "Le parametre transmis contient : " say mess pop_eh .end coruscant chris$ parrot exception.pir Debut du programme. Gestion de l'exception. Le parametre transmis est de type : Exception Le parametre transmis contient : Grosse faute. coruscant chris$
Un gestionnaire est référencé par un label au sens PIR du terme, et le flux du programme va se brancher à l'emplacement ainsi spécifié lorsque survient l'exception considérée.
Si cette exception est due à un problème de programmation, le message qui est récupéré contient la description du problème.
Par exemple dans le cas d'une division par zéro :
coruscant chris$ cat zerodiv.pir .sub main :main say "Debut du programme." push_eh GESTIONNAIRE $P1 = new ['Exception'] $P2 = new ['String'] setattribute $P1, 'message', $P2 $I1 = 10 / 0 end GESTIONNAIRE: .local pmc mess .get_results (mess) say "Gestion de l'exception." print "Le parametre transmis contient : " say mess pop_eh .end coruscant chris$ parrot zerodiv.pir Gestion de l'exception. Le parametre transmis contient : Divide by zero coruscant chris$
Une nouvelle fois, il est facile de constater que l'évènement ne se différencie pas des objets classiques gérés par des PMC
Les gestionnaires sont positionnés au moment voulu avec toutes leurs caractéristiques et, si une exception se présente, elle sera prise en compte.
Force est de constater que le langage intermédiaire de la machine Parrot offre de nombreuses possibilités.
Bien qu'une nouvelle mise à jour de la machine virtuelle soit disponible mensuellement, PIR ne devrait pas, dans l'immédiat, voir apparaître des modifications majeures.
Parrot Intermediate Representation
n'est ni un Langage évolué,
ni un assembleur. C'est une
représentation à base de registres typés qui sait aussi gérer
les espaces de noms et qui a l'avantage de proposer à l'utilisateur
un nombre illimité de registre symboliques. Sa capacité de créer de
multiples gestionnaires pour prendre en compte les divers évènements
qui peuvent se présenter et les outils de mise au point disponibles
permettent de créer des applications avec un minimum de difficultés.
=head1 Références
[poo] Programmation orientée objet, http://fr.wikipedia.org/wiki/Orienté_objet
[Parrot] Site de la machine virtuelle, http://www.parrot.org
[Noms] Les espaces de nom, http://fr.wikipedia.org/wiki/Espace_de_noms
[Rakudo] Le site de Rakudo, Perl6, http://www.rakudo.org
[Langages] Liste des langages, http://www.parrot.org/languages
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.