Polymorphisme et valeurs de retour des fonctions
Le polymorphisme paramétrique d'Objective CAML permet de définir des
fonctions dont le type de retour est complètement
indéterminé. Par exemple :
# let
id
x
=
x
;;
val id : 'a -> 'a = <fun>
Cependant, le type de retour dépend du type
de l'argument. Ainsi, lorsque l'on applique la fonction id
à un argument, le mécanisme d'inférence de types sait instancier
la variable de type 'a. Donc, pour chaque utilisation
particulière, le type de id peut être déterminé.
S'il n'en était pas ainsi, l'utilisation du typage statique fort,
chargé d'assurer la sûreté d'exécution, n'aurait plus de sens.
En effet, une fonction d'un type entièrement indéterminé comme
'a -> 'b autoriserait n'importe quelle conversion de type qui
amènerait inévitablement à une erreur durant l'exécution
puisque la représentation physique des valeurs de type différents
ne sont pas les mêmes.
Apparente contradiction
Cependant, il est possible de définir dans le langage Objective CAML des
fonctions dont le type de retour contient une variable de type
n'apparaissant pas dans le type de ses arguments. Nous allons en
considérer quelques exemples et voir pourquoi une telle
possibilité n'est pas contradictoire avec le typage statique fort.
Voici un premier exemple :
# let
f
x
=
[]
;;
val f : 'a -> 'b list = <fun>
Cette fonction permet de construire une valeur polymorphe à partir de
n'importe quoi :
# f
()
;;
- : '_a list = []
# f
"n'importe quoi"
;;
- : '_a list = []
Néanmoins, la valeur obtenue n'est pas entièrement
indéterminée : il s'agit d'une liste. Elle ne pourra donc pas
être utilisée n'importe où.
Voici trois exemples de fonctions dont le type est celui redouté 'a -> 'b :
# let
rec
f1
x
=
f1
x
;;
val f1 : 'a -> 'b = <fun>
# let
f2
x
=
failwith
"n'importe quoi"
;;
val f2 : 'a -> 'b = <fun>
# let
f3
x
=
List.hd
[]
;;
val f3 : 'a -> 'b = <fun>
Ces fonctions ne sont, en fait pas dangereuses, vis-à-vis de la
sûreté d'exécution car il n'est pas possible de les utiliser
pour construire une valeur : la première boucle, les deux
dernières déclenchent une exception qui interrompt le calcul.
C'est également pour ne pas pouvoir définir de fonction de type
'a -> 'b que les nouveaux constructeurs d'exceptions ne
peuvent pas avoir d'argument dont le type contient une variable.
En effet, si l'on pouvait déclarer une exception polymorphe
Poly_exn de type 'a -> exn, on pourrait alors écrire la
fonction :
let
f
=
function
0
->
raise
(Poly_exn
false)
|
n
->
n+
1
;;
La fonction f étant de type int -> int et
Poly_exn étant de type 'a -> exn, on pourrait alors
définir :
let
g
n
=
try
f
n
with
Poly_exn
x
->
x+
1
;;
Cette fonction est également bien typée (puisque l'argument de
Poly_exn peut être quelconque) et, dès lors, l'évaluation
de (g 0) aboutirait à la tentative d'additionner un entier et
un booléen !