next up previous
Next: Le polymorphisme paramétrique Up: Typage des objets en Previous: Pourquoi faire du typage?

  
Polymorphisme dans les langages à objets

Un des aspects le plus difficiles à saisir de la sémantique des langages objets concerne le caractère polymorphe de certaines constructions. Dans la mesure où le polymorphisme en général englobe plusieurs notions très différentes et parfois difficiles à faire co-exister, cet aspect des langages objets reste terriblement obscur dans une grande majorité d'ouvrages qui leurs sont dediés.

Par ailleurs c'est dans la sosphistication des constructions polymorphes que les objets d'Ocaml montrent tous leurs avantages. Nous commencons donc cette partie du cours par un description intuitive du polymorphisme dans les langages objets.

Considérons l'exemple suivant d'héritage entre classes:

class point init =
 object
   val mutable x = init
   method getx = x
   method move d = x<-x+d
end;;

class colorpoint x (cinit : string)  =
 object
   inherit point x
   val mutable c = cinit
   method getcolor = c
   method setcolor c' = c<-c'
end;;

let origin (p : #point) = p#getx = 0;;

La notation (p :#point) est lue

p:Type_Instance_de(point)

Considérons le code

let monpoint = new point 0;

let monCP = new colorpoint 0  "rouge";;

origin(monpoint);;
origin(monCP);;
La fonction origin attend un objet instance de point (par exemple, monpoint). Peut-on appliquer cette fonction aux objets de type colorpoint? On peut considérer l'appel origin(monCP) comment une tentative de re-utiliser la fonction avec un type différent de celui pour lequel elle a été intialement conçue. Dans un langage classique, cet appel provoque une erreur de typage car les types Type_Instance_de(point) et Type_Instance_de(colorpoint) sont considérés incompatibles car différents. Dans les langages objet, cet appel est valide grâce à une interprétation plus fexible du typage, connue sous le nom de polymorphisme.

Polymorphisme: Une fonction, une méthode ou un objet est polymorphe si on peut l'employer avec plusieurs types. Par exemple, une fonction qui donne la longueur d'un tableau est polymorphe en Ocaml: elle peut-être employeée sur n'importe quel type de tableau. Les fonctions ou méthodes qui ont un seul type sont dites monomorphes.

Avant d'aller plus avant dans l'étude de cette notion, voyons qu'elle est problème posé ici par une interprétation ``plus flexible du typage''. Le typage a en charge la vérification de cohérence de types d'un programme dans le but de diminuer les erreurs. Lorsqu'il est est statique, le typage doit garantir qu'un programme correctement typé ne produira pas d'erreurs dus au typage pendant l'exécution. Cela est communément appelé sécurité du typage, et est une propriété indispensable du typage statique. Elle impose que toute flexibilité dans l'interprétation du typage, c.a.d., tout polymorphisme, doit préserver la sécurité du typage. Donc, si on veut utiliser une valeur ou fonction avec plusieurs types, cela doit se faire de manière sûre, sans provoquer d'erreurs dus au typage pendant l'exécution. Considérons une fonction f quelconque, définie par:

let f (o : t) = ...code avec opérations sûr o ...

f est polymorphe si, par la suite, le code suivant est bien typé (et donc, ne produit pas d'erreur à l'exécution)

f(a) a : t' et $t' \not = t$.

Quelles conditions doit remplir le type t' pour que cet usage soit sûr? Les informations dont on dispose sont

Dans les langages objets (mais aussi dans d'autres paradigmes de programmation), on trouve trois possibilités pour expliquer la forme qui prendront t et t' afin de permettre cet usage polymorphe avec les impératifs de sécurité du typage.

Polymorphisme paramétrique:
si les opérations effectuées sur o ne sont pas dépendantes de sont type t (p.e: une recopie, une comparaison par égalité), alors, le type de f est polymorphe paramétrique. Intuitivement, un tel type est paramétré par une variable de type qui represente n'importe quel type. Par la suite, on peut utiliser f avec tout type qui est une instance de ce type polymorphe, à savoir, tout type obtenu par substitution des paramètres ou variables de type par un type éffectif quelconque. Une instance est un type plus spécialisé. Ainsi, pour ce mécanisme de typage, la propriété quet et t' doivent vérifier est: ``t' est une instance polymorphe de t''.
Exemple: En Ocaml, les variables de type sont notées 'a, 'b, etc. La fonction let f (x,y) = (x=y) de type t = 'a * 'a -> bool peut être utilisée de manière polymorphe, par un appel f ("a", "ab"). Dans ce cas, t' est string * string -> bool.

Polymorphisme d'inclusion:
si le type t' est tel que toutes les opérations sur t sont aussi définies sur t', alors on peut utiliser n'importe quel objet a:$\,t'$ là où un objet de type t était attendu. Prenons t = point et considérons la fonction
let origin (p) = (p:>point)#getx = 0;;
La notation (p:>point) est une contrainte sur le type de p. Elle est lue le type de doit être un sous-type de point. Un sous-type t' de point est un type d'objet tel que Regardons ces conditions sous l'angle de la sécurité du typage. Considérons la première: supposons que t' contient au moins toutes les méthodes de point. Le compilateur a utilisé la contrainte (p:>point) pour n'accepter que les méthodes de point en tant que messages envoyés à p dans le code de origin. Ainsi, un message envoyé par origin vers un objet de t' est nécessairement une méthode de point, qui est donc aussi dans t'. Cet envoi ne produit donc pas d'échec à l'exécution, et de ce point de vue là, l'usage de t' à la place de point est sûr. Par exemple t' peut être colorpoint.
# let pc = new point_colore 4 "blue";;

# origin (pc);;
- : bool = false
Examinons la deuxième condition. Si la méthode qui est envoyé vers l'objet de t' possède un type compatible avec celui dans point, il n'y aura pas d'incompatibilité de typage par rapport au comportément attendu par le code de origin. En effet, cette fonction attend que le type résultat de getx soit int, autrement, il est impossible de réaliser la comparaison ...#getx = 0. La méthode getx a le type int aussi bien dans point que dans colorpoint. Si cela n'avait pas été le cas, un objet de type colorpoint aurait été refusé en argument de origin.

Surcharge ou polymorphisme ad-hoc:
On dit qu'un nom ou symbole d'opérateur est surchargé s'il a associés plusieurs codes différents, un par chaque type admis par la fonction 1. Par exemple, on peut surcharger l'opérateur + avec le code de trois fonctions: l'une qui réalise l'addition entre deux entiers, l'autre entre deux rééls et la dernière qui fait l'addition entre nombres complexes. Chacun des codes est différent et leurs types n'ont pas de rapport entre eux. Pour chaque utilisation de l'opérateur surchargé, le compilateur doit décider quel code executer, et ceci selon le type des arguments donnés. Dans ce mécanisme de typage polymorphe il n'y a pas de lien entre les types t et t'.

Les deux premières formes de polymorphisme ont en commun une propriété très importante: une fonction polymorphe peut être appliquée sans changement de son code à une infinité de types d'arguments. Ainsi, le polymorphisme paramétrique et le polymorphisme d'inclusion sont des moyens de partager aussi bien du code source que du code executable. Ces deux formes de polymorphisme appartiennent à un même catégorie plus générale, dite polymorphisme universel.

Le polymorphisme ad-hoc se distingue des deux formes précédentes en ce que le code des fonctions n'est pas partagé. En effet, un symbole surchargé se voit associer autant de codes que de types distincts sur lesquels il veut pouvoir s'appliquer. Cette forme de polymorphisme est donc moins générale que le polymorphisme universel.

  Polymorphisme  
  $\swarrow$ $\searrow$  
Universel   Ad-hoc
$\swarrow$ $\searrow$   $\downarrow$
Paramétrique Sous-typage   Surcharge




 
next up previous
Next: Le polymorphisme paramétrique Up: Typage des objets en Previous: Pourquoi faire du typage?
Maria-Viginia Aponte
2001-04-10