3.3 Le langage Kermeta
3.3.3 Classes et opérations génériques
Comme il a été discuté précédemment, nous avons choisi pour des raisons de
tes-tabilité et de fiabilité de vérifier statiquement le typage des expressions Kermeta. Pour
permettre le typage statique d’expression, un certain nombre de déclarations telles que
les déclarations de type pour les variables, doit être rendu obligatoire. Cependant il faut
porter attention à ne pas surcharger le langage avec trop d’informations redondantes
sous peine de nuire gravement à son utilisabilité. Cette section discute du typage
sta-tique d’expression de navigation dans les modèles et présente le système de types que
nous avons choisi dans le langage Kermeta.
Exemple et motivations
Fig. 3.14 –Un méta-modèle très simple.
Considérons le méta-modèle très simple présenté sur la figure 3.14. Ce méta-modèle
contient deux classes et une association entre ces deux classes. Voici un exemple
d’ex-pression que l’on souhaite pouvoir écrire :
myA.b.one.label
Cette expression permet de naviger l’association entre les classesAetB et retourne le
label associé à une instance de B. L’opération onepermet d’extraire un élément d’une
collection. Dans le système de types proposé par EMOF, chaque classe définit un type.
Essayons donc de typer les différentes parties de cette expression en utilisant ce système
de type. Le type demyAestA. L’expressionmyA.bretourne un ensemble d’instances de
B, son type est donc la classeSet. On suppose que la classe Set possède une méthode
one qui retourne un élément de l’ensemble. L’expression myA.b.one retourne un objet
contenu dans l’ensemble, son type, donné par le type de retour de la méthodeone de
la classe Set, est Object. Étant donné ce résultat, l’appel à l’attribut label sera refusé
dans l’expressionmyA.b.one.labelcar le typeObject ne contient pas d’attribut label.
Dans l’exemple précédent, l’expression est refusée car le système de types ne permet
pas de représenter que le type demyA.bn’est pas un ensemble d’objets arbitraires mais
un ensemble deB. Une façon de résoudre ce problème, sans modifier le système de type,
est d’imposer à l’utilisateur de spécifier le type des objets extraits de la collection. On
pourrait écrire par exemple, si l’on suppose que l’on dispose d’une pseudo-opération
o.asType(T)qui permet d’indiquer que l’objet o est de typeT :
myA.b.one.asType(B).label
Dans le cas de cette expression, l’utilisateur indique explicitement que myA.b.one est
de typeBet l’expression dans son ensemble sera acceptée car une propriétélabel existe
bien sur la classeB. Cependant, cette approche n’est pas satisfaisante pour deux raisons.
Premièrement l’information est inutilement dupliquée puisque l’association sur le
méta-modèle de la figure 3.14 exprime déjà le fait que la propriété b de la classe A dénote
un ensemble d’instances deB. Deuxièmement, d’un point de vue purement syntaxique,
cela surcharge grandement les expressions de navigation ce qui tend à les rendre moins
naturelles et moins lisibles. La seule solution reste alors de modifier le système de types
pour permettre de typer statiquement les expressions de navigation. Dans cette optique
deux solutions peuvent être envisagées.
Le langage Kermeta 67
La première solution est d’introduire dans le système de types des types spéciaux
pour représenter les collections. Cela peut être vu comme une extension de la notion de
tableau dans les langages orientés-objets. C’est le choix qui a été fait dans les langages
tel que Xion [MSFB05] pour permettre de typer statiquement les expressions de
navi-gation dans les modèles UML. Cependant cela impose d’introduire des constructions
syntaxiques et sémantiques dédiées aux collections et de définir des politiques
appro-priées pour la vérification des types. Pour ces raisons, nous avons rejeté cette option
pour le langage Kermeta au profit des classes paramétriques qui offrent une solution
plus générale.
La seconde solution est de permettre la définition de classes paramétrées. De cette
façon, les collections typées peuvent être définies comme n’importe quelle autre classe.
La généricité offre une solution élégante au typage statique et est aujourd’hui adoptée
par la plupart des langages orientés-objets statiquement typés tel que Java, Eiffel, C++
ou C]. Cependant, introduire la généricité dans le système de types de EMOF n’est pas
trivial car cela impose de modifier en profondeur la relation entre classes et types.
Dans le cas de l’expressionmyA.b.one.label, en utilisant des collections génériques
le type de myA.b est une collection deB (Set<B>), myA.b.one est donc bien de type
B.et la propriétélabel peut être lue sans problèmes.
Ajout de la généricité à EMOF
Comme indiqué sur la figure 2.10, dans le système de types de EMOF un type
peut être un type primitif (PrimitiveType), une énumération (Énumération) ou une
classe (Class). L’introduction des classes paramétriques rend plus complexe la relation
entre types et classes. Si par exemple on définit une classeSet<G>, qui représente un
ensemble d’éléments de typeG, cette classe ne définit pas un type Set mais engendre
un ensemble de types en fonction de la valeur que prend le paramètre de typeG.
La figure 3.15 présente l’approche adoptée pour créer un système de types
compa-tible avec celui d’EMOF et incluant la généricité. Le cadre A représente le modèle de
classes de EMOF. La classeClass hérite de Type et contient un ensemble de Features,
à savoir des propriétés et des opérations. Le cadre B représente un système de types
supportant la généricité. Ce modèle fait explicitement la différence entre une définition
de classe (ClassDefinition) qui contient des propriétés, des opérations et des variables
de type (TypeVariable) et une classe paramétrée (ParametrizedClass) qui définit un
type. Une classe paramétrée fait référence à une définition de classe et associe un type
effectif à chacune des variables de type de cette définition de classe par l’intermédiaire
de la classe TypeVariableBinding. Dans ce modèle, la notion de définition de classe et
la notion de type, c’est-à-dire de classe paramétrée, sont distinctes.
Sur la figure 3.15 les liens pointillés entre le modèle de classe de EMOF dans le cadre
A et le modèle permettant la généricité dans le cadre B, figurent les éléments communs
à ces deux modèles. Ainsi on retrouve sur les deux modèles, les notions de Type et de
Feature mais la classe Classe de EMOF semble contenir à la fois des caractéristiques
d’une définition de classe car elle contient desFeatures mais aussi des caractéristiques
d’une classe paramétrée car elle hérite de Type. Afin d’ajouter la généricité à EMOF
Le langage Kermeta 69
l’idée est donc de séparer la classeClass en deux pour séparer la notion de type de la
notion de classe.
Le cadre C sur la figure 3.15 présente le résultat de la composition du modèle de
généricité dans EMOF. On a gardé le nomClass pour désigner une classe paramétrée
afin d’assurer la compatibilité avec MOF et on a ajouté les classes ClassDefinition,
TypeVariable etTypeVariableBinding afin de représenter les définitions de classes. Afin
d’assurer la compatibilité avec EMOF nous avons ajouté à la classeClassdes propriétés
dérivées (dont le nom est préfixé par un / sur la figure) permettant d’accéder aux
propriétés telles queisAbstract ou ownedFeature qui étaient disponibles sur les classes
EMOF et qui ont été déplacées dans la classe ClassDefinition.
Nous avons choisi d’intégrer la généricité à Kermeta non seulement parce que cela
fournit une solution au problème du typage des expressions de navigation mais aussi,
parce que cela fournit un mécanisme plus général qui offre une solution élégante au
traitement d’expressions intégrant des itérateurs analogues à ceux d’OCL. Les principes
de la généricité implantés dans Kermeta sont peu différents des concepts présents dans
des langages tel que Eiffel, .NET 2.0 ou Java 5.
Classes génériques en Kermeta
La figure 3.16 illustre l’utilisation des classes paramétriques à travers la définition
de files d’attentes. La ligne 2 montre la définition de la classe Queue comportant un
paramètre de type G. La variable de type G peu être utilisée comme n’importe quel
type à l’intérieur de la classe. A la ligne 4, par exemple, on définit une association vers
un ensemble ordonné d’objets de typeG. Le typeGest également utilisé comme type de
paramètre pour l’opérationenqueueet comme type de retour de l’opérationdequeue. La
sous-classeSortedQueue, définie à la ligne 17, est également une file d’attente mais avec
une politique différente. Pour pouvoir utiliser cette file d’attente, il faut que les éléments
que l’on met en attente soient munis d’un opérateur de comparaison qui détermine leur
priorité. De la même manière que pour la classeQueue, on définit une classe générique,
mais, pour imposer l’existence d’une opération permettant de comparer les éléments
deux-à-deux, on contraint la variable de types aux sous-types du type Comparable.
C’est cette contrainte qui permet de comparer deux éléments à la ligne 22.
Opérations génériques en Kermeta
L’utilisation de variables de types ne se limite pas aux seules classes mais permet
également de définir des opérations paramétriques. L’intérêt de définir de telles
opéra-tions est de permettre de donner au vérificateur de type le lien existant entre le type
des paramètres et le type de retour d’une opération. La figure 3.17 présente la définition
d’une opération max permettant de comparer deux éléments entre eux et de rendre le
plus grand des deux. De la même manière que pour les classes dans l’exemple
précé-dent, pour pouvoir comparer les éléments entre eux, on a contraint le typeT à être un
sous-type deComparable.
1 /* * Generic FIFO queue */
2 c l a s s Queue <G >
3 {
4 reference elements : oset G [*]
5
6 opération enqueue ( e : G) : Void i s do
7 elements . add (e)
8 end
9
10 opération dequeue () : G i s do
11 r e s u l t := elements . first
12 elements . removeAt (0)
13 end
14 }
15
16 /* * Generic Sorted queue */
17 c l a s s SortedQueue <C : Comparable > i n h e r i t s Queue <C >
18 {
19 method enqueue (e : C ) : Void i s do
20 var i : Integer
21 from i := 0
22 u n t i l i == elements . size or e > elements . elementAt (i )
23 loop
24 i := i + 1
25 end
26 elements . addAt (i , e)
27 end
28 }
Fig. 3.16 –Définition de classes paramétriques
1 c l a s s Utils {
2
3 /* * Returns max (a , b) */
4 opération max <T : Comparable >( a : T , b : T ) : T i s do
5 r e s u l t := i f a > b then a e l s e b end
6 end
7 }
Le langage Kermeta 71
contexte d’une classe et dans le contexte d’une opération. Dans le cas des variables de
classes, le type effectif doit être précisé explicitement par le programmeur. Ainsi pour
instancier une file d’entiers on écrira :
var fifo : Queue<Integer> init Queue<Integer>.new
Dans le cas des opérations génériques le type effectif est inféré lors de la vérification
de type à partir du type des paramètres effectifs. Pour appeler l’opérationmax définie
précédemment, on écrira donc simplement :
var m : Integer init max(2, 3)
Le type demax(2, 3) est Integer. Ce type est inféré à partir des types des littéraux 2
et3. En effet, l’opérationmax définie sur le listing de la figure 3.17 définie une variable
de type T qui est utilisé pour le type des deux paramètres et le type de retour de
l’opération. Lors de l’appel max(2, 3), le type T est associé au type Integer car les
expression 2 et3 sont de type Integer. Le type de retour de l’opération max étant T,
le type calculé pour l’expressionmax(2, 3) est bienInteger.
Pour permettre l’inférence du type associé a chaque variable de type pour chaque
appel, il est nécessaire d’utiliser toutes les variables de type d’une opération dans les
types des paramètres de cette opération. Cette limitation est peu handicapante en
pratique car il n’existe à notre connaissance que de très rares cas d’utilisation pour
lequel il est interessant de ne pas utiliser les variables de types dans le type d’au moins
un paramètre.
Dans le document
Langage et méthode pour une ingénierie des modèles fiable
(Page 68-74)