• Aucun résultat trouvé

Classes et opérations génériques

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.