Types cycliques
Objective CAML permet de déclarer des structures de données récursives,
c'est à dire des structures pouvant comporter une valeur ayant la
même structure.
# type
somme_ex1
=
Ctor
of
somme_ex1
;;
type somme_ex1 = | Ctor of somme_ex1
# type
record_ex1
=
{
champ
:
record_ex1
}
;;
type record_ex1 = { champ: record_ex1 }
La construction de valeurs de ces types n'est pas immédiate car il
est nécessaire de posséder une valeur pour en construire une. La
déclaration récursive de valeurs permet de se tirer de cercle vicieux.
# let
rec
somme_val
=
Ctor
somme_val
;;
val somme_val : somme_ex1 = Ctor (Ctor (Ctor (Ctor (Ctor ...))))
# let
rec
val_record_1
=
{
champ
=
val_record_2
}
and
val_record_2
=
{
champ
=
val_record_1
}
;;
val val_record_1 : record_ex1 = {champ={champ={champ={champ={champ=...}}}}}
val val_record_2 : record_ex1 = {champ={champ={champ={champ={champ=...}}}}}
Les arbres planaires généraux peuvent se représenter par une
structure de données de cette forme.
# type
'a
arbre
=
Sommet
of
'a
*
'a
arbre
list
;;
type 'a arbre = | Sommet of 'a * 'a arbre list
# let
hauteur_1
=
Sommet
(0
,[]
)
;;
val hauteur_1 : int arbre = Sommet (0, [])
# let
hauteur_2
=
Sommet
(0
,[
Sommet
(1
,[]
);
Sommet
(2
,[]
);
Sommet
(3
,[]
)
]
)
;;
val hauteur_2 : int arbre =
Sommet (0, [Sommet (1, []); Sommet (2, []); Sommet (3, [])])
# let
hauteur_3
=
Sommet
(0
,[
hauteur_2;
hauteur_1
]
)
;;
val hauteur_3 : int arbre =
Sommet
(0,
[Sommet (0, [Sommet (...); Sommet (...); Sommet (...)]); Sommet (0, [])])
(* idem avec un record *)
# type
'a
arbre_rec
=
{
etiquette:
'a
;
fils:
'a
arbre_rec
list
}
;;
type 'a arbre_rec = { etiquette: 'a; fils: 'a arbre_rec list }
# let
haut_rec_1
=
{
etiquette=
0
;
fils=[]
}
;;
val haut_rec_1 : int arbre_rec = {etiquette=0; fils=[]}
# let
haut_rec_2
=
{
etiquette=
0
;
fils=[
haut_rec_1]
}
;;
val haut_rec_2 : int arbre_rec = {etiquette=0; fils=[{etiquette=0; fils=[]}]}
On pourrait se dire qu'il n'est pas utile d'avoir un type
énuméré avec un seul constructeur mais, par défaut, Objective CAML
n'accepte pas les abréviations de types récursives.
# type
'a
arbre
=
'a
*
'a
arbre
list
;;
Characters 7-36:
The type abbreviation arbre is cyclic
On peut définir des valeurs ayant cette structure, mais elles n'ont
pas le même type.
# let
arbre_1
=
(0
,[]
)
;;
val arbre_1 : int * 'a list = 0, []
# let
arbre_2
=
(0
,[
(1
,[]
);
(2
,[]
);
(3
,[]
)
]
)
;;
val arbre_2 : int * (int * 'a list) list = 0, [1, []; 2, []; 3, []]
# let
arbre_3
=
(0
,[
arbre_2;
arbre_1
]
)
;;
val arbre_3 : int * (int * (int * 'a list) list) list =
0, [0, [...; ...; ...]; 0, []]
De la même manière, Objective CAML n'est pas capable d'inférer un type
à une fonction prenant en argument une valeur de cette forme.
# let
max_list
=
List.fold_left
max
0
;;
val max_list : int list -> int = <fun>
# let
rec
hauteur
=
function
Sommet
(_,[]
)
->
1
|
Sommet
(_,
fils)
->
1
+
(max_list
(List.map
hauteur
fils))
;;
val hauteur : 'a arbre -> int = <fun>
# let
rec
hauteur2
=
function
(_,[]
)
->
1
|
(_,
fils)
->
1
+
(max_list
(List.map
hauteur2
fils))
;;
Characters 97-101:
This expression has type 'a list but is here used with type
('b * 'a list) list
Le message d'erreur nous montre que la fonction hauteur2 serait
typable si nous avions l'égalité entre les types 'a et
'b * 'a list. C'est justement cette égalité qui nous a
été refusée dans la déclaration de l'abréviation de type
arbre.
Pourtant, le typage des objets permet de construire des valeurs dont le
type est cyclique. Examinons la fonction suivante et tentons
de deviner son type.
# let
f
x
=
x#copy
=
x
;;
Le type de x est une classe qui possède la méthode
copy. Le type de cette méthode est le même que celui de
x puisque l'on teste leur égalité. En conséquence, si
foo est le type de x il est de la forme
< copy : foo ; .. >. D'après ce que nous avons vu plus
haut, son type étant cyclique, cette fonction devrait être
rejetée; et pourtant il n'en est rien.
# let
f
x
=
x#copy
=
x
;;
val f : (< copy : 'a; .. > as 'a) -> bool = <fun>
Objective CAML accepte cette fonction et dénote la cyclicité du type
par le as qui identifie 'a avec un type contenant
'a.
En réalité, les deux problèmes sont exactement les mêmes, mais
par défaut, Objective CAML n'accepte ce genre de types que lorsqu'ils
concernent des objets. La fonction hauteur est typable si
elle produit une cyclicité sur le type d'un objet.
# let
rec
hauteur
a
=
match
a#fils
with
[]
->
1
|
l
->
1
+
(max_list
(List.map
hauteur
l))
;;
val hauteur : (< fils : 'a list; .. > as 'a) -> int = <fun>