Chaque valeur est d'un certain type. Par exemple 5 est un int
, "super"
est de type string
, et ref 3
est de type int ref
. Pour les fonctions, c'est pareil, chacune d'elle va avoir un type. Ce type dépendra de deux choses : d'une part des types des paramètres que la fonction prend, et d'autre part du type de valeur que la fonction retourne. Lorsque la fonction ne retourne rien, on utilisera par convention le mot "unit"
pour traduire cette absence de valeur. Voyons tout de suite une série d'exemples.
La fonction absolue
:
let absolue x = if x >= 0 then x else -x in
prend en paramètre un entier, et renvoie un entier. Le type d'une telle fonction s'écrit par convention de la manière suivante : ce qu'elle prend en paramètre, flèche vers la droite, ce qu'elle donne. C'est pourquoi le type de absolue
est " int -> int "
, ce qui se lit ``int donne int''.
Une petite astuce permet de vérifier que c'est bien le type int -> int
qui a été déduit par le compilateur pour la fonction absolue
. L'idée est de faire exprès d'afficher une erreur de typage, de manière à obtenir un message du genre "this expression has type ... but is here used with type ..."
. Essayons par exemple l'instruction print_int absolue
, qui est absurde puisqu'une fonction n'est pas un entier, et ne peut donc sûrement pas être affiché avec print_int
.
let absolue x = if x >= 0 then x else -x in print_int absolue;
File "test.ml", line 6, characters 10-17: This expression has type int -> int but is here used with type int
L'expression ligne 6 entre les caractères 10 et 17 est le nom de la fonction absolue
. Comme prévu, cette expression est de type int
donne int
. On a ici essayé d'utiliser print_int
dessus, qui attend un int
, et on a donc obtenu une erreur de type.
La règle de typage est exactement la même pour les fonctions qui réalisent des actions avant de renvoyer une valeur :
let somme_entiers n = let total = ref 0 in for i = 1 to n do total := !total + i; done; !total in
est aussi de type " int -> int"
.
Reprenons maintenant la fonction affiche_ligne
:
let affiche_ligne motif = for colonne = 1 to nb_colonnes do print_string motif; done; in
Cette fonction prend en paramètre une chaîne, nommée motif
, et ne renvoie rien : elle se
"unit"
. Donc
le type de la fonction affiche_ligne
est : " string -> unit "
.
Le type "unit"
sert également dans le cas des fonctions qui n'ont pas de paramètres. Ne pas avoir de paramètres est considéré comme avoir un paramètre de type unit
. Tout simplement :
let separation () = print_newline(); print_string "- - - - -"; print_newline(); in
est de type " unit -> unit "
.
La fonction recup_somme
:
let recup_somme () = let x = read_int() in let y = read_int() in x + y in
est elle de type " unit -> int "
.
Dernier point, les fonctions à plusieurs paramètres : on met les type des paramètres les uns à la suite des autres, séparés par des flèches, que l'on lit toujours "donne". Et on indique toujours le type de la valeur de retour à la fin. Ainsi :
let moyenne a b = (a +. b) /. 2.0 in
est de type " float -> float -> float "
.
Autre exemple :
let affiche_ligne_motif nb_colonnes motif = for colonne = 1 to nb_colonnes do print_string motif; done; print_newline(); in
est de type " int -> string -> unit "
.
Remarque : il n'y a jamais qu'une seule valeur de retour pour une fonction. Si on veut vraiment retourner plusieurs valeurs d'un coup, il faudra les regrouper dans une structure, et cette structure sera alors considérée comme une seule valeur. Mais on n'étudiera pas cela tout de suite.
Quels sont les types des fonctions prédéfinies qui suivent ?
print_newline print_int print_float print_string read_int read_float read_line
Écrivez-les avant de lire les réponses qui sont données ci-dessous.
Par convention, on écrit le type d'une valeur après le nom de la valeur suivi d'un deux-points.
print_newline : unit -> unit print_int : int -> unit print_float : float -> unit print_string : string -> unit read_int : unit -> int read_float : unit -> float read_line : unit -> string
Quels sont les types de ces autres fonctions prédéfinies :
abs (* valeur absolue d'un entier *) pred (* prédécesseur d'un entier *) abs_float (* valeur absolue d'un réel *) sqrt (* racine carrée d'un réel *) cos (* cosinus d'un réel *) float_of_int (* réel à partir d'un entier *) int_of_float (* entier à partir d'un réel *)
Écrivez-les avant de lire les réponses qui sont données ci-dessous.
abs : int -> int pred : int -> int abs_float : float -> float sqrt : float -> float cos : float -> float float_of_int : int -> float int_of_float : float -> int
Certaines fonctions ont des types un peu particuliers. C'est le cas de la fonction maximum
, qui a la propriété de fonctionner aussi bien avec des paramètres int
que des paramètres float
:
let maximum x y = if x > y then x else y in
En regardant l'erreur que nous donnerait une instruction "print_int maximum;"
, on apprend que la fonction maximum
a pour type :
'a -> 'a -> 'a
Le symbole 'a
veut dire : "un type donné". Le type de cette fonction traduit le fait que la fonction doit prendre deux arguments du même type, pour qu'une comparaison puisse être effectuée, et renvoie une valeur du même type que les arguments. Ainsi maximum
peut être vue comme une une fonction de type int -> int -> int
ou float -> float -> float
, mais ne peut pas être vue comme du int -> float -> float
, par exemple.
On aura l'occasion de reparler de cette propriété d'utilisation de types indéterminés plus tard. On verra que c'est un aspect très puissant de Caml. Pour l'instant, il faut juste savoir que ça existe pour ne pas être dérouté par certains messages d'erreurs.