 
 
 
 Types et généricité
L'intérêt de la programmation par objet vient d'une part de la
modélisation d'un problème par les relations d'agrégation et
d'héritage, mais aussi de la possibilité de réutilisation et de
modification de comportement des classes. L'extension objet d'Objective CAML
doit de surcroît conserver les propriétés de typage statique du
langage.
Les classes abstraites permettent de factoriser du code mais
aussi de regrouper dans un même << protocole de communication >> leurs
sous-classes : une classe abstraite fixe les noms et types des
messages que pourront recevoir les instances de ses
héritières. Cette dernière notion sera encore plus appréciée avec
l'héritage multiple.
La notion de type objet ouvert ou, plus simplement, de
type ouvert, définissant les méthodes requises, 
autorise la manipulation d'instances par des fonctions génériques. 
Cependant, il faudra parfois préciser des contraintes de
type. Ce sera indispensable avec les classes paramétrées qui
reprennent la généricité du polymorphisme paramétrique dans le cadre
des classes. Ce dernier trait de la couche objet d'Objective CAML lui permet
d'être vraiment générique.
 Classes et méthodes abstraites
Les classes abstraites sont des classes dont certaines méthodes sont
déclarées mais ne possèdent pas de corps. Ces méthodes sont
alors dites abstraites. Il n'est pas possible d'instancier une classe
abstraite : new est interdit. On utilise le mot clé
virtual pour signifier l'abstraction d'une classe ou d'une
méthode.
 Syntaxe 
 
class virtual nom = 
object ...end
Une classe doit impérativement être déclarée abstraite dès
lors que l'une de ses méthodes l'est. On déclare une méthode
abstraite en donnant uniquement son type attendu.
 Syntaxe 
 
method virtual nom : type
Lorsqu'une une sous-classe d'une classe abstraite redéfinit toutes
les méthodes abstraites de son ancêtre, alors elle peut devenir
concrète, sinon elle doit elle aussi être déclarée abstraite.
Nous voulons construire un ensemble d'objets affichables disposant
tous d'une méthode print qui affichera le contenu de
l'objet traduit en chaîne de caractères. Ces objets
devront donc posséder une méthode
to_string.
Nous définissons à cet effet la classe printable.
Suivant la nature des objets considérés, la chaîne à
construire pourra varier. La méthode to_string sera donc
abstraite dans la déclaration de printable.
Du coup, cette classe est également abstraite.
# class virtual printable () =
   object(self)
     method virtual to_string : unit -> string
     method print () = print_string (self#to_string())
   end ;;
class virtual printable :
  unit ->
  object
    method print : unit -> unit
    method virtual to_string : unit -> string
  end
Notons que le caractère abstrait de la classe et de sa méthode
to_string est manifeste dans le type obtenu.
À partir de cette classe on cherche à définir la hiérarchie de
classes de la figure 15.4.
Figure 15.4 : relations entre classes d'objets affichables
On redéfinit facilement les classes point,
colored_point et picture. Il suffit d'ajouter dans
leurs déclarations la ligne inherit printable () pour
les doter par héritage de la méthode print.
# let p = new point (1,1) in p#print() ;;
( 1, 1)- : unit = ()
# let pc = new colored_point (2,2) "bleu" in pc#print() ;;
( 2, 2) de couleur bleu- : unit = ()
# let t = new picture 3 in t#add (new point (1,1)) ; 
                          t#add (new point (3,2)) ; 
                          t#add (new point (1,4)) ;
                          t#print() ;;
[ ( 1, 1) ( 3, 2) ( 1, 4)]- : unit = ()
La sous-classe rectangle suivante hérite de
printable et définit la méthode to_string.
Les variables d'instance llc et ruc désignent respectivement
le point en bas à gauche et le point en haut à droite du rectangle.
# class rectangle (p1,p2) = 
   object
     inherit printable ()
     val mutable llc = (p1 : point)
     val mutable ruc = (p2 : point)
     method to_string () = "[" ^ p1#to_string() ^ "," ^ p2#to_string() ^ "]"
   end ;;
class rectangle :
  point * point ->
  object
    val mutable llc : point
    val mutable ruc : point
    method print : unit -> unit
    method to_string : unit -> string
  end
La classe rectangle hérite de la classe abstraite
printable, et donc récupère la méthode
print. Elle possède deux variables d'instance
point : les coins inférieur gauche (llc) et
supérieur droit (ruc). Sa méthode to_string envoie
le message to_string à ses variables d'instance point.
# let r = new rectangle (new point (2,3), new point (4,5));;
val r : rectangle = <obj>
# r#print();;
[( 2, 3),( 4, 5)]- : unit = ()
 Classes, types et objets
Rappelons que le type d'un objet est le type de ses méthodes. Par exemple le 
type
point, inféré lors de la déclaration de la classe
point, est une abréviation du type : 
  point =
    < distance : unit -> float; get_x : int; get_y : int;
      moveto : int * int -> unit; rmoveto : int * int -> unit;
      to_string : unit -> string  >
 
C'est un type objet fermé. C'est-à-dire que la totalité
des méthodes et des types associés qui le composent est déterminée ; il
n'y en a pas d'autre. 
Le mécanisme d'inférence de types, lors d'une déclaration de
classe, calcule le type fermé associé à cette classe. 
 Types ouverts
L'envoi d'un message à un objet fait partie intégrante du
langage, on peut donc définir une fonction envoyant un message
quelconque à un objet dont le type est non précisé.
# let f x = x#get_x ;;
val f : < get_x : 'a; .. > -> 'a = <fun>
Le type inféré pour l'argument de f est un type objet,
puisque l'on envoie un message à x, mais ce type objet est
ouvert. Le paramètre x de la fonction f doit
au moins posséder une méthode get_x. Comme le résultat de
l'envoi de ce message n'est pas utilisé dans la fonction f,
son type est le plus général possible (c'est-à-dire une variable de
type 'a). Le mécanisme d'inférence de types laisse donc la
possibilité d'utiliser la fonction f avec n'importe quel
objet possédant une méthode get_x. L'ouverture du type de
x est signifiée par les deux points (..) à la fin du type
< get_x : 'a; .. >
# f (new point(2,3)) ;;
- : int = 2
# f (new colored_point(2,3) "vert émeraude") ;;
- : int = 2
# class c () =
   object
     method get_x = "J'ai une méthode get_x" 
   end ;;
class c : unit -> object method get_x : string end
# f (new c ()) ;;
- : string = "J'ai une m\233thode get_x"
L'inférence de types pour les classes peut engendrer des types ouverts, 
en particulier pour les valeurs initiales de construction des instances.
L'exemple suivant construit une classe couple, 
dont les valeurs initiales a 
et b possèdent une méthode to_string.
# class couple (a,b) =
   object
     val p0 = a
     val p1 = b
     method to_string() = p0#to_string() ^ p1#to_string()
     method copy () = new couple (p0,p1)
   end ;;
class couple :
  (< to_string : unit -> string; .. > as 'a) *
  (< to_string : unit -> string; .. > as 'b) ->
  object
    val p0 : 'a
    val p1 : 'b
    method copy : unit -> couple
    method to_string : unit -> string
  end
 
Les types de a et b sont deux types ouverts possédant
la méthode to_string. On note que ces deux types sont
considérés comme différents. Ils sont notés respectivement comme
as 'a et  as 'b. Les variables de
types 'a et 'b sont contraintes par le type ouvert
engendré.
On désigne le type ouvert construit à partir d'un type fermé
obj_type en utilisant le symbole dièse :
 Syntaxe 
 
 #obj_type 
Le type obtenu contient toutes les méthodes du type
obj_type et se termine par deux points. 
 Contrainte de type
Nous avons montré au chapitre sur la programmation fonctionnelle (voir
page ??) comment contraindre une expression à
posséder un type plus précis que celui engendré par l'inférence. Les
types objet, fermés ou ouverts, peuvent être utilisés pour effectuer
de telles contraintes. On peut vouloir a priori ouvrir le type
d'un objet défini pour lui appliquer une méthode à venir. On utilise
alors une contrainte de type objet ouvert :
 Syntaxe 
 
(nom:#type)
Ce qui permet d'écrire :
# let g (x : #point) = x#amess;;
val g :
  < amess : 'a; distance : unit -> float; get_x : int; get_y : int;
    moveto : int * int -> unit; print : unit -> unit;
    rmoveto : int * int -> unit; to_string : unit -> string; .. > ->
  'a = <fun>
La contrainte de type avec #point force x
à avoir au moins toutes les méthodes de point, et
l'envoi du message amess ajoute une méthode au type du
paramètre x.
Comme dans le reste du langage, l'extension objet d'Objective CAML offre un
typage statique obtenu par inférence. Lorsque ce mécanisme ne
dispose pas de l'information suffisante pour déterminer le type
d'une expression, il lui assigne une variable de type. Nous venons de
voir que ce phénomène est aussi valable pour le typage des
objets. Il peut engendrer parfois des situations de blocage que
l'utilisateur devra résoudre en donnant explicitement des
informations de type.
# class a_point p0 =
   object
     val p = p0
     method to_string() = p#to_string() 
   end ;;
Characters 6-89:
Some type variables are unbound in this type:
  class a_point :
    (< to_string : unit -> 'b; .. > as 'a) ->
    object val p : 'a method to_string : unit -> 'b end
The method to_string has type unit -> 'a where 'a is unbound
On sort de ce blocage en précisant que le paramètre p0
est de type #point.
# class a_point (p0 : #point) =
   object
     val p = p0
     method to_string() = p#to_string()
   end ;;
class a_point :
  (#point as 'a) -> object val p : 'a method to_string : unit -> string end
Pour éviter de placer les contraintes de type à plusieurs endroits dans la
déclaration d'une classe, on utilise la syntaxe suivante :
 Syntaxe 
 
 constraint type1 = type2
L'exemple précédent peut s'écrire en indiquant que le paramètre 
p0 est de type 'a, puis en posant une contrainte
de type sur la variable 'a.
# class a_point (p0 : 'a) =
   object
     constraint 'a = #point
     val p = p0
     method to_string() = p#to_string()
   end ;;
class a_point :
  (#point as 'a) -> object val p : 'a method to_string : unit -> string end
Plusieurs contraintes de type peuvent être posées dans une déclaration de classe.
 Warning 
 
Un type ouvert ne peut pas apparaître comme type d'une méthode. 
Cette contrainte forte provient du fait qu'un type ouvert contient une
variable de type non encore instanciée provenant de la suite du
type. Comme on ne peut avoir une variable de type libre dans une
déclaration de type, une méthode contenant un tel type est rejetée par
l'inférence de types. 
# class b_point p0  =
   object
     inherit a_point p0
     method get = p
   end ;;
Characters 6-77:
Some type variables are unbound in this type:
  class b_point :
    (#point as 'a) ->
    object val p : 'a method get : 'a method to_string : unit -> string end
The method get has type #point where .. is unbound
Le type de get est en fait, à cause de la contrainte
constraint 'a = #point, le type ouvert
#point. Celui-ci contient la variable de type libre dénotée
par le double point (..) ce qui n'est pas autorisé.
 Héritage et type de self
Il existe une exception pour l'apparition d'une variable de type dans
le type des méthodes. Il s'agit du cas où une variable désigne le
type de l'objet lui-même (self). Prenons l'exemple d'une
méthode d'égalité qui teste si un point est égal à un autre.
# class point_eq (x,y) = 
   object (self : 'a) 
     inherit point (x,y)
     method eq (p:'a) = (self#get_x = p#get_x) && (self#get_y = p#get_y)
   end ;;
class point_eq :
  int * int ->
  object ('a)
    val mutable x : int
    val mutable y : int
    method distance : unit -> float
    method eq : 'a -> bool
    method get_x : int
    method get_y : int
    method moveto : int * int -> unit
    method print : unit -> unit
    method rmoveto : int * int -> unit
    method to_string : unit -> string
  end
Le type de la méthode eq est 'a -> bool, mais la
variable de type désigne le type de l'instance lors de sa
construction.
On peut hériter de la classe point_eq et redéfinir 
la méthode eq dont le type est toujours paramétré par 
le type de l'instance. 
# class colored_point_eq (xc,yc) c = 
   object (self : 'a) 
     inherit point_eq (xc,yc) as super 
     val c = (c:string)
     method get_c = c
     method eq (pc : 'a) =  (self#get_x = pc#get_x) && (self#get_y = pc#get_y) 
                         && (self#get_c = pc#get_c)
   end ;;
class colored_point_eq :
  int * int ->
  string ->
  object ('a)
    val c : string
    val mutable x : int
    val mutable y : int
    method distance : unit -> float
    method eq : 'a -> bool
    method get_c : string
    method get_x : int
    method get_y : int
    method moveto : int * int -> unit
    method print : unit -> unit
    method rmoveto : int * int -> unit
    method to_string : unit -> string
  end
La méthode eq, de la classe colored_point_eq, est
toujours du type 'a -> bool, mais, cette fois, la variable
'a désigne le type d'une instance de la classe
colored_point_eq. La définition de eq de la classe
colored_point_eq masque celle héritée. Les méthodes
contenant le type de l'instance dans leur type sont appelées méthodes
binaires. Elles créeront des limitations à la relation de sous-typage
décrite à la page ??.
 Héritage multiple
L'héritage multiple permet d'hériter des champs de données et des
méthodes de plusieurs classes. En cas de noms de champs ou de
méthodes identiques, seulement la dernière déclaration, dans l'ordre
des déclarations d'héritages, sera conservée. Néanmoins, on peut
référencer une méthode d'une des classes ancêtres en associant des
noms différents à chaque classe dont on hérite. Cela n'est pas
valable pour les variables d'instance. Si une classe héritée masque
une variable d'instance d'une classe précédemment héritée, cette
dernière variable n'est plus accessible directement. Les différentes
classes héritées n'ont pas forcément de lien d'héritage entre
elles. L'intérêt de l'héritage multiple est d'augmenter la
réutilisabilité des classes.
On définit la classe abstraite geometric_object qui
 déclare deux méthodes virtuelles compute_area et
 compute_circ pour le calcul de la surface et du
 périmètre.
# class virtual geometric_object () =
 object
   method virtual compute_area : unit -> float
   method virtual compute_circ : unit -> float
 end;;
On redéfinit alors la classe rectangle de la manière
suivante : 
# class rectangle_bis ((p1,p2) :'a) = 
 object
   constraint 'a = point * point
   inherit printable ()
   inherit geometric_object ()
   val mutable llc = p1
   val mutable ruc = p2
   method to_string () = 
    "["^p1#to_string()^","^p2#to_string()^"]"
   method compute_area() = 
    float ( abs(ruc#get_x - llc#get_x) * abs(ruc#get_y - llc#get_y))
   method compute_circ() = 
    float ( (abs(ruc#get_x - llc#get_x) + abs(ruc#get_y - llc#get_y)) * 2)
 end;;
class rectangle_bis :
  point * point ->
  object
    val mutable llc : point
    val mutable ruc : point
    method compute_area : unit -> float
    method compute_circ : unit -> float
    method print : unit -> unit
    method to_string : unit -> string
  end
Cette implantation de classes respecte le graphe d'héritage de la figure 15.5.
Figure 15.5 : héritage multiple
Pour ne pas avoir à réécrire la méthode de la classe
rectangle, on peut hériter directement de rectangle
comme le montre le schéma de la figure 15.6.
Figure 15.6 : héritage multiple (suite)
Dans ce cas là, seules les méthodes abstraites de la classe
abstraite geometric_object doivent être définies dans
rectangle_ter.
# class rectangle_ter (p2 :'a) = 
   object
   constraint 'a = point * point
   inherit rectangle p2 
   inherit geometric_object () 
   method compute_area() = 
    float ( abs(ruc#get_x - llc#get_x) * abs(ruc#get_y - llc#get_y))
   method compute_circ() = 
    float ( (abs(ruc#get_x - llc#get_x) + abs(ruc#get_y - llc#get_y)) * 2)
 end;;  
Toujours dans la même veine, les développements de la hiérarchie
printable et geometric_object auraient
pu être séparés jusqu'au moment où il devenait utile d'avoir une
classe possédant les deux comportements. Le schéma de la figure
15.7 montre les relations ainsi définies.
Figure 15.7 : héritage multiple (fin)
Si on suppose que les classes printable_rect et
geometric_rect définissent des variables d'instance pour
les coins d'un rectangle, on se retrouve dans la classe
rectangle avec quatre points (deux par coin). 
class rectangle_quarte (p1,p2) =
 inherit printable_rect (p1,p2) as super_print
 inherit geometric_rect (p1,p2) as super_geo
end;;
Dans le cas où des méthodes de même type existent dans les deux
classes ..._rect, alors seule la dernière est
visible. Néanmoins, grâce au nommage des classes ancêtres
(super_...), il est toujours possible d'invoquer une
méthode de l'une ou l'autre des ancêtres.
L'héritage multiple permet donc une factorisation du code en
intégrant les méthodes déjà écrites de différentes classes ancêtres
pour construire une nouvelle entité. Le prix à payer est la taille
des objets construits plus gros que nécessaire : duplication de
champs, héritage de champs inutiles à une application donnée, etc...
De plus, en cas de duplication, comme dans notre dernier exemple, il
faut établir manuellement la communication entre ces champs (mise à
jour, etc...). Dans le dernier exemple sur la classe
rectangle, on obtient les variables d'instance de la classe
rectangle_graph et rectangle_geo. Si l'une de ces
classes possède une méthode qui modifie ces variables (comme un
facteur d'échelle) alors il est nécessaire de répercuter ces
modifications aux variables héritées de l'autre classe. Cette trop
lourde communication entre variables d'instance héritées souligne
souvent une mauvaise modélisation du problème posé.
 Classes paramétrées
Les classes paramétrées permettent d'utiliser le polymorphisme
paramétrique d'Objective CAML dans les classes. Une déclaration de classe
peut être paramétrée par des variables de type, comme dans une
déclaration de types d'Objective CAML. Cela offre de nouvelles
possibilités de généricité, pour la réutilisation du code,
et s'intègre dans le typage à la ML quand l'inférence de types
produit des types paramétrés.
La syntaxe diffère légèrement de la déclaration de types paramétrés,
les paramètres de type sont entre crochets.
 Syntaxe 
 
class ['a, 'b, ...]
nom = object ...end
Néanmoins le type Objective CAML est noté comme à l'habitude :
('a,'b,...) nom. 
Par exemple, si l'on désire construire une classe pair pour
décrire les paires, une solution naïve consiste à poser :
# class pair x0 y0 =
   object
     val x = x0    
     val y = y0
     method fst = x
     method snd = y
   end ;;        
Characters 6-106:
Some type variables are unbound in this type:
  class pair :
    'a ->
    'b -> object val x : 'a val y : 'b method fst : 'a method snd : 'b end
The method fst has type 'a where 'a is unbound
On retrouve l'erreur de typage évoquée à la définition de la classe
a_point (page ??). Le
message d'erreur indique que la variable de type 'a assignée
au paramètre x0 (et donc à x et
fst) n'est pas liée. 
Pour obtenir un typage correct, comme
dans le cas des types paramétrés, il faut paramétrer la classe
pair avec deux variables de type et forcer le type des
paramètres de construction x0 et y0 :
# class ['a,'b] pair (x0:'a) (y0:'b) =
   object
     val x = x0
     val y = y0
     method fst = x
     method snd = y
   end ;;
class ['a, 'b] pair :
  'a ->
  'b -> object val x : 'a val y : 'b method fst : 'a method snd : 'b end
L'affichage de l'inférence de types indique une interface de classe
paramétrée par les variables de type 'a et 'b.
Quand on construit une valeur d'une classe paramétrée, les paramètres
de type sont instanciés par les types des paramètres de construction :
# let p = new pair 2 'X';;
val p : (int, char) pair = <obj>
# p#fst;;
- : int = 2
# let q = new pair 3.12 true;;
val q : (float, bool) pair = <obj>
# q#snd;;
- : bool = true
 Remarque 
 
Dans les déclarations de classe, les paramètres de type sont notés
entre crochets, alors qu'au niveau de l'affichage des types, ils se
retrouvent entre parenthèses.
 Héritage de classes paramétrées
L'héritage d'une classe paramétrée doit indiquer les paramètres de
cette classe. On définit une classe acc_pair, qui hérite de
('a,'b) pair et ajoute deux méthodes d'accès aux champs ,
get1 et get2,
# class ['a,'b] acc_pair (x0 : 'a) (y0 : 'b) = 
   object
     inherit ['a,'b] pair x0 y0 
     method get1 z = if x = z then y else raise Not_found
     method get2 z = if y = z then x else raise Not_found
   end;;
class ['a, 'b] acc_pair :
  'a ->
  'b ->
  object
    val x : 'a
    val y : 'b
    method fst : 'a
    method get1 : 'a -> 'b
    method get2 : 'b -> 'a
    method snd : 'b
  end
# let p = new acc_pair 3 true;;
val p : (int, bool) acc_pair = <obj>
# p#get1 3;;
- : bool = true
On peut préciser les paramètres de type de la classe paramétrée héritée,
par exemple pour une paire de points.
# class pair_point (p1,p2) = 
   object
     inherit [point,point] pair p1 p2
   end;;
class pair_point :
  point * point ->
  object
    val x : point
    val y : point
    method fst : point
    method snd : point
  end
La classe pair_point n'a plus besoin de paramètres de type,
car les paramètres 'a et 'b sont complètement
déterminés.
Pour construire des paires d'objets affichables, c'est-à-dire
possédant une méthode print, on réutilise la classe abstraite
printable (voir page ??), puis 
on définit la classe printable_pair qui hérite de
pair.
# class printable_pair (x0 ) (y0 ) = 
   object
     inherit [printable, printable] acc_pair x0 y0
     method print () = x#print(); y#print ()
   end;;
Cette implantation permet effectivement de construire des paires
d'instances de printable, mais ne peut pas être utilisée pour
des objets d'une autre classe et possédant une méthode print.
Un premier essai consiste à ouvrir le type printable
utilisé comme paramètre de type de acc_pair :
# class printable_pair (x0 ) (y0 ) = 
   object
     inherit [ #printable, #printable ] acc_pair x0 y0
     method print () = x#print(); y#print ()
   end;;
Characters 6-149:
Some type variables are unbound in this type:
  class printable_pair :
    (#printable as 'a) ->
    (#printable as 'b) ->
    object
      val x : 'a
      val y : 'b
      method fst : 'a
      method get1 : 'a -> 'b
      method get2 : 'b -> 'a
      method print : unit -> unit
      method snd : 'b
    end
The method fst has type #printable where .. is unbound
Cet essai échoue car les méthodes fst et snd contiennent
un type ouvert.
Nous allons donc conserver les paramètres de type de la classe, tout en les 
contraignant au type ouvert #printable.
# class ['a,'b] printable_pair (x0 ) (y0 ) = 
   object
     constraint 'a = #printable
     constraint 'b = #printable 
     inherit ['a,'b] acc_pair x0 y0
     method print () = x#print(); y#print ()
   end;;
class ['a, 'b] printable_pair :
  'a ->
  'b ->
  object
    constraint 'a = #printable
    constraint 'b = #printable
    val x : 'a
    val y : 'b
    method fst : 'a
    method get1 : 'a -> 'b
    method get2 : 'b -> 'a
    method print : unit -> unit
    method snd : 'b
  end
On construit alors une paire affichable contenant un point et un 
point coloré.
# let pp = new printable_pair 
            (new point (1,2)) (new colored_point (3,4) "vert");;
val pp : (point, colored_point) printable_pair = <obj>
# pp#print();;
( 1, 2)( 3, 4) de couleur vert- : unit = ()
 Classes paramétrées et typage
Une classe paramétrée est, du point de vue des types, un type paramétré. 
Une valeur d'un tel type peut alors contenir des variables de type faibles.
# let r = new pair [] [];;
val r : ('_a list, '_b list) pair = <obj>
# r#fst;;
- : '_a list = []
# r#fst = [1;2];;
- : bool = false
# r;;
- : (int list, '_a list) pair = <obj>
Une classe paramétrée est aussi vue comme un type objet fermé, 
donc rien n'empêche 
de l'utiliser aussi comme type ouvert avec la notation dièse.
# let compare_rien ( x : ('a, 'a) #pair) = 
   if x#fst = x#fst then x#mess else x#mess2;;
val compare_rien : < fst : 'a; mess : 'b; mess2 : 'b; snd : 'a; .. > -> 'b =
  <fun>
Ce qui nous amène à pouvoir construire des types paramétrés contenant des variables
de type faibles, tout en étant des types objet ouverts.
# let jolitype x ( y : ('a, 'a) #pair) = if x = y#fst then y else y;;
val jolitype : 'a -> (('a, 'a) #pair as 'b) -> 'b = <fun>
Si on applique cette fonction à un seul paramètre, on obtient une
fermeture dont les variables de type deviennent faibles. Un type
ouvert, comme #pair, contient encore une partie à instancier
représentée par les deux points (..). En cela un type ouvert est un
paramètre de type dont une partie est déjà connue. Lors de
l'affaiblissement d'un tel type suite à une application partielle, 
l'afficheur précise que la variable de type représentant ce type
ouvert est affaiblie. La notation est alors _#pair.
# let g = jolitype 3;;
val g : ((int, int) _#pair as 'a) -> 'a = <fun>
Si maintenant on applique la fonction g à une paire, on
modifie son type faible. 
# g (new acc_pair 2 3);;
- : (int, int) acc_pair = <obj>
# g;;
- : (int, int) acc_pair -> (int, int) acc_pair = <fun>
On ne peut plus alors utiliser g sur des paires simples.
# g (new pair 1 1);;
Characters 4-16:
This expression has type (int, int) pair = < fst : int; snd : int >
but is here used with type
  (int, int) acc_pair =
    < fst : int; get1 : int -> int; get2 : int -> int; snd : int >
Only the second object type has a method get1
Enfin comme les paramètres de la classe paramétrée peuvent eux aussi
s'affaiblir, on obtient l'exemple suivant.
# let h = jolitype [];;
val h : (('_b list, '_b list) _#pair as 'a) -> 'a = <fun>
# let h2 = h (new pair [] [1;2]);;
val h2 : (int list, int list) pair = <obj>
# h;;
- : (int list, int list) pair -> (int list, int list) pair = <fun>
Le type du paramètre de h n'est plus ouvert. L'application
suivante n'est pas typable car l'argument n'est pas de type pair.
# h (new acc_pair [] [4;5]);;
Characters 4-25:
This expression has type
  ('a list, int list) acc_pair =
    < fst : 'a list; get1 : 'a list -> int list; get2 : int list -> 'a list;
      snd : int list >
but is here used with type
  (int list, int list) pair = < fst : int list; snd : int list >
Only the first object type has a method get1
 Remarque 
 
Les classes paramétrées d'Objective CAML sont absolument nécessaires dès que
l'on manipule des méthodes dont le type comporte une variable de type
autre que le type de self.
 
 
