English version
Accueil     À propos     Téléchargement     Ressources     Contactez-nous    

FAQ - Langage de base

Types de base

Comment faire des calculs en précision arbitraire ?
OCaml et Caml Light disposent tous deux d'une bibliothèque de calcul en précision arbitraire précision, sur les nombres rationnels. La bibliothèque se nomme Num en OCaml et camlnum en Caml Light.
Les opérations sur les grands nombres sont suffixées par le caractère /: par exemple l'addition se note +/. On crée des grands nombres par conversion à partir d'entiers ou de chaînes de caractères. Pour l'affichage dans le toplevel, il peut être utile d'utiliser un imprimeur. Un exemple d'utilisation pour OCaml est donné plus bas.
$ ocaml nums.cma

open Num;;
open Format;;
let print_num ff n = fprintf ff "%s" (string_of_num n);;
print_num : Format.formatter -> num -> unit = <fun>;
#install_printer print_num;;
num_of_string "2/3";;
- : Num.num = 2/3
let n = num_of_string "1/3" +/ num_of_string "2/3";;
n : Num.num = 1
let rec fact n =
 if n <= 0 then (num_of_int 1) else num_of_int n */ (fact (n - 1));;
fact : int -> Num.num = <fun>;
fact 100;;
- : Num.num =
9332621544394415268169923885626670049071596826438162146859296389521759999322991
5608941463976156518286253697920827223758251185210916864000000000000000000000000

Structures de données

Mon tableau est modifié sans raison
En général cela provient d'un partage physique passé inaperçu. En Caml, il n'y a jamais de recopie implicite de tableaux. Si donc l'on donne deux noms à un tableau, toute modification citant l'un des noms se répercute sur l'autre:
let v = Array.make 3 0;;
val v : int array = [|0; 0; 0|]
let w = v;;
val w : int array = [|0; 0; 0|]
w.(0) <- 4;;
- : unit = ()
v;;
- : int array = [|4; 0; 0|]
Le partage physique apparaît également au niveau des éléments stockés dans les cases d'un tableau : quand ces cases elles-mêmes contiennent des vecteurs, le partage de ces vecteurs implique qu'une modification de l'un se répercute sur les autres (voir également la question suivante).
Comment faire des tableaux à deux dimensions ?
La seule manière de procéder est de créer un tableau de tableaux (les tableaux de Caml sont unidimentionnels: ce ne sont que des vecteurs au sens mathématique). Si l'on écrit naïvement la création d'un tableau à plusieurs dimensions, le résultat n'est pas celui attendu, car on crée sans le vouloir un partage physique des lignes du tableau (voir aussi l'entrée précédente) :
let m = Array.make 2 (Array.make 3 0);;
m : int array array = [|[|0; 0; 0|]; [|0; 0; 0|]|]
m.(0).(0) <- 1;;
- : unit = ()
m;;
- : int array array = [|[|1; 0; 0|]; [|1; 0; 0|]|]
En effet l'allocation d'un tableau consiste à calculer la valeur d'initialisation et à la mettre dans chaque case du tableau (c'est pourquoi la ligne de la matrice qui est allouée par l'expression Array.make 3 0 est unique et physiquement partagée par toutes les cases du tableau m.
La solution est d'utiliser la primitivem make_matrix, qui fabrique directement une matrice dont les cases sont remplies par la valeur d'initialisation fournie. L'autre solution consiste à écrire le programme qui allouera explicitement une nouvelle ligne pour chaque ligne du tableau. Par exemple:
let matrix n m init =
  let result = Array.make n (Array.make m init) in
    for i = 1 to n - 1 do
      result.(i) <- Array.make m init
    done;
    result;;
matrix : int -> int -> 'a -> 'a array array = <fun>
De même, la fonction Array.copy ne donne pas le résultat attendu pour des matrices: il faut écrire une fonction de copie qui copie explicitement le contenu de toutes les lignes de la matrice:
let copy_matrix m =
  let l = Array.length m in
    if l = 0 then m else
      let result = Array.make l m.(0) in
        for i = 0 to l - 1 do
           result.(i) <- Array.copy m.(i)
        done;
        result

Définitions de types

Comment définir un type énuméré ?
Un type énuméré est un type somme avec uniquement des constructeurs constants. Par exemple, voici un type avec 3 constantes :
type color = Blue | White | Red;;
type color = Blue | White | Red
Blue;;
- : color = Blue
Les noms Blue, White et Red sont des constructeurs du type color. On peut définir des fonctions sur ce type par filtrage :
let string_of_color = function
    Blue -> "blue"
  | White -> "white"
  | Red -> "red";;
Comment partager une étiquette entre deux types enregistrement ?
Lorsqu'on définit des types qui partagent un label, le label du deuxième type cache celui du premier. Par exemple :
type point_3d = {x : float; y : float; z : float};;
type point_2d = {x : float; y : float};;

{x = 10.; y = 20.; z = 30.};;
The record field label z belongs to the type point_3d
but is here mixed with labels of type point_2d
Le moyen le plus simple de contourner cette difficulté est ... d'employer des noms différents ! Par exemple
type point3d = {x3d : float; y3d : float; z3d : float};;
type point2d = {x2d : float; y2d : float};;
Avec OCaml, on peut proposer deux autres solutions. Tout d'abord, il est possible d'utiliser les modules pour définir les deux types dans des espaces de noms différents:
module D3 = struct
 type point = {x : float; y : float; z : float}
end;;

module D2 = struct
 type point = {x : float; y : float}
end;;
Les labels peuvent être maintenant qualifiés avec le nom de leur module, et D3.x est différencié de D2.x :
{D3.x = 10.; D3.y = 20.; D3.z = 30.};;
- : D3.point = {D3.x = 10.000000; D3.y = 20.000000; D3.z = 30.000000}
{D2.x = 10.; D2.y = 20.};;
- : D2.point = {D2.x = 10.000000; D2.y = 20.000000}
On peut aussi adopter les objets, qui permettent la surcharge des noms de méthodes :
class point_3d ~x ~y ~z = object
  method x : float = x
  method y : float = y
  method z : float = z
end;;

class point_2d ~x ~y = object
  method x : float = x
  method y : float = y
end;;
En ce cas on obtient plus que la surcharge: on peut définir des fonctions polymorphes qui travaillent aussi bien sur les points de dimension 2 que de dimension 3, et l'on peut aussi considérer les points 3D comme des points 2D (à l'aide d'une simple coercion de type).
Comment définir deux types sommes qui partagent un nom de constructeur ?
En règle générale, on ne peut pas. Il faut donc utiliser des noms de constructeurs uniques dans tout le module qu'on écrit. À moins bien sûr de définir les deux types en question dans deux modules différents. Comme dans le cas des étiquettes (discuté ci-dessus), on obtient alors des constructeurs qu'on peut différencier en les qualifiant par le module auquel ils appartiennent. En OCaml, une autre solution consiste à utiliser des « variantes polymorphes », c'est-à-dire des constructeurs qui ne sont pas définis lors de la définition du type (ces constructeurs sont en quelque sorte préexistants). Par exemple :
type ids = [ `Name | `Val ];;
type person = [ `Name of string ];;
let (f : person -> string) = function `Name s -> s;;
val f : person -> string = <fun>
let (is_name : ids -> bool) = function `Name -> true | _ -> false;;
val is_name : ids -> bool = <fun>

Fonctions et procédures

Comment définir une fonction ?
En Caml, les définitions de fonctions sont très proches de la syntaxe des mathématiques: on introduit la définition par le mot-clef let, suivi du nom de la fonction et de ses arguments; enfin la formule de calcul de l'image de l'argument est introduite par un signe =.
let successor (n) = n + 1;;
successor : int -> int = <fun>
En fait, les parentèses autour de l'argument peuvent être omises, de telle sorte qu'on écrit en général :
let successor n = n + 1;;
successor : int -> int = <fun>
Comment définir une fonction récursive ?
Il faut explicitement signaler la récursion: on utilise « let rec » au lieu de « let ». Par exemple :
let rec fact n =
  if n = 0 then 1 else n * fact (n - 1);;
fact : int -> int = <fun>
let rec fib n =
  if n <= 1 then n else fib (n - 1) + fib (n - 2);;
fib : int -> int = <fun>
Les fonctions peuvent être mutuellement récursives :
let rec odd n =
  if n = 0 then true
  else if n = 1 then false else even (n - 1)
and even n =
  if n = 0 then false
  else if n = 1 then true else odd (n - 1);;
odd : int -> bool = <fun>
even : int -> bool = <fun>
Comment appliquer une fonction ?
On applique les fonctions comme en mathématiques en écrivant le nom de la fonction suivi de son argument entre parenthèses: f (x). En pratique les parenthèses ne sont obligatoires que lorsque l'expression argument x est complexe. On les omet donc lorsqu'il s'agit d'une constante ou d'une variable: on écrit fib 2 au lieu de fib (2) et fact x au lieu de fact (x).
Lorsque l'expression argument d'une fonction est plus complexe qu'un simple identificateur, il faut mettre cet argument entre parenthèses. Cela est valable en particulier si l'argument d'une fonction est un nombre négatif: il faut le parenthéser. L'application de f à -1 s'écrit donc f (-1) et non f -1 qui est interprété exactement comme f - 1 (c'est donc une soustraction).
Comment définir une procédure ?
Rappelons qu'on appelle traditionnellement procédure, toute commande qui produit un effet (par exemple une impression au terminal ou la modification d'une case de la mémoire) mais n'a pas de résultat mathématiquement significatif.
En Caml il n'y a pas à proprement parler de différence entre procédure et fonction: une procédure est un cas particulier de fonction qui a pour résultat la valeur triviale « rien » c'est-à-dire (). Par exemple la primitive print_string qui imprime une chaîne de caractères au terminal ne rend que () pour tout résultat, indiquant ainsi qu'elle a effectué le travail demandé.
Les procédures qui ne nécessitent pas d'argument significatif ont encore une fois () pour argument. Par exemple, la procédure print_newline qui passe une ligne au terminal n'a pas d'argument significatif: elle est de type unit -> unit.
Les procédures sont définies exactement comme des fonctions. Par exemple:
let message s = print_string s; print_newline();;
message : string -> unit = <fun>
message "Hello world!";;
Hello world!
- : unit = ()
Comment définir une procédure/fonction qui ne prend aucun argument ?
Remarquez qu'il est impossible de définir en Caml une procédure sans argument: sa définition donnerait lieu à son exécution, et on ne pourrait plus l'appeler ultérieurement. Dans l'exemple suivant double_newline vaut (), et son évaluation ne produit pas de retour chariots.
let double_newline = print_newline(); print_newline();;
@
@
double_newline : unit = ()
double_newline;;
- : unit = ()
La définition et l'utilisation correcte de la procédure serait :
let double_newline () = print_newline(); print_newline();;
double_newline : unit -> unit = <fun>
double_newline;;
- : unit -> unit = <fun>
double_newline ();;
@
@
- : unit = ()
Comment définir une function à plusieurs arguments ?
Il suffit d'écrire la liste des arguments successifs lors de la déclaration de la fonction. Par exemple :
let sum x y = x + y;;
sum : int -> int -> int = <fun>
et de fournir les arguments dans le bon ordre à l'application :
sum 1 2;;
- : int = 3
Ces fonctions sont appelées fonctions curryfiées, par opposition aux fonctions ayant des n-uplets pour argument :
let sum' (x, y) = x + y;;
sum' : int * int -> int = <fun>
sum' (1, 2);;
- : int = 3
Comment définir une fonction qui a plusieurs résultats ?
Vous pouvez définir une fonction qui retourne une paire ou un n-uplet :
let div_mod x y = (x quo y, x mod y);;
div_mod : int -> int -> int * int = <fun>
div_mod 15 7;;
- : int * int = 2, 1
Qu'est-ce qu'une « fonction anonyme » ?
Une fonction peut n'avoir pas de nom: on parle alors d'une valeur fonctionnelle ou d'une fonction anonyme. Une valeur fonctionnelle est introduite par le mot clef function, suivi de l'argument de la fonction, d'une flèche -> et du corps de la fonction. Par exemple :
function x -> x + 1;;
- : int -> int = fun
(function x -> x + 1) 2;;
- : int = 3
Quelle est la différence entre fun et function ?
Les fonctions s'introduisent normalement par le mot-clé function. Chaque paramètre de la fonction est introduit par une nouvelle construction function. Par exemple :
function x -> function y -> ...
définit une fonction à deux paramètres x et y. Les fonctions qui procèdent par filtrage s'introduisent également par le mot-clé function.
Le mot-clé fun introduit des fonctions curryfiées (à plusieurs arguments successifs). Par exemple :
fun x y -> ...
introduit une fonction à deux arguments x et y comme la précédente.
Ma fonction n'est jamais appliquée
Cela provient la plupart du temps d'un argument oublié: du fait du caractère fonctionnel de Caml, il n'y a pas d'erreur lorsqu'on demande l'évaluation d'une fonction sans tous ses arguments: dans ce cas une valeur fonctionnelle est retournée, mais la fonction n'est évidemment pas appelée. Exemple caricatural: print_newline sans argument ne provoque pas d'erreur mais ne fait rien. Un avertissement est néanmoins émis par le compilateur dans les cas flagrants de mauvaise utilisation.
#print_newline;;
- : unit -> unit
#print_newline ();;
@
- : unit = ()

Filtrage

Comment emboîter des filtrages ?
Il faut absolument parenthéser un filtrage qui se trouve à l'intérieur d'un autre filtrage. En effet le filtrage le plus interne « capture » toutes les clauses de filtrage qui le suivent. Par exemple :
let f = function
 | 0 -> match ... with | a -> ... | b -> ...
 | 1 -> ...
 | 2 -> ...;;
est compris comme
let f = function
 | 0 ->
     match ... with
     | a -> ...
     | b -> ...
     | 1 -> ...
     | 2 -> ...;;
Cette erreur peut se produire dans tous les cas de constructions qui impliquent du filtrage: function, match .. with et try ... with. En général on parenthèse le filtrage fautif à l'aide des mots-clefs begin et end. On écrira donc :
let f = function
 | 0 ->
     begin match ... with
     | a -> ...
     | b -> ...
     end
 | 1 -> ...
 | 2 -> ...;;

Exceptions

Typage

Erreur : Un type est incompatible avec lui-même.
On obtient parfois le message: cette expression est de type « un certain type » mais est utilisée avec « le même un certain type ». Ceci se produit souvent lorsqu'on travaille dans le système interactif.
La raison que deux types ayant le même nom a été déclaré. Le compilateur ne les confond pas, mais ne peut faire autrement que les imprimer de la même manière puisqu'ils ont le même nom. Considérez par exemple:
type t = T of int
type t = T of int
let x = T 1;;
val x : t = T 1
type t = T of int;;
type t = T of int
let incr = function T x -> T (x+1);;
val incr : t -> t = <fun>
incr x;;
This expression has type t but is here used with type t
Ce phénomène apparaît souvent lorsqu'on utilise le système interactif en rechargeant de multiples fois le même fichier (ce qui redéfinit les types de données). La solution est alors de recommencer une nouvelle session.
Une fonction obtenue par application partielle n'est pas suffisamment polymorphe
Le cas le plus fréquent où une définition n'est pas assez polymorphe se produit lors de la définition d'une fonction par application partielle d'une fonction polymorphe plus générale. En Caml le polymorphisme est introduit uniquement par la construction « let », et le résultat d'une application est seulement faiblement polymorphe. En conséquence la fonction résultant de l'application ne peut pas être polymorphe. Ici il suffit de définir la fonction en rendant la fonctionnalité explicite pour le typeur : en général on ajoute une construction function ou un paramètre supplémentaire (cette transformation est appelée eta-expansion):
let map_id = List.map (function x -> x) (* Result is weakly polymorphic *)
val map_id : '_a list -> '_a list = <fun>
map_id [1;2]
- : int list = [1;2]
map_id (* No longer polymorphic *)
- : int list -> int list = <fun>
let map_id' l = List.map (function x -> x) l
val map_id' : 'a list -> 'a list = <fun>
map_id' [1;2]
- : int list = [1;2]
map_id' (* Still fully polymorphic *)
- : 'a list -> 'a list = <fun>
La nouvelle définition est sémantiquement équivalente à l'ancienne, et peut recevoir un schéma de type polymorphe étant donné que ce n'est plus une application.
Le type d'une expression contient des variables de types qui ne peuvent pas être généralisées
Ce message est émis lorsque le compilateur Caml essaie de compiler une fonction ou une valeur qui est monomorphe, mais pour laquelle certains types n'ont pu être complètement inferrés. Des variables de types apparaissent alors dans le type, qu'on appelle « faibles » (et qu'on marque par un souligné : '_a). Elles disparaitront grâce au mécanisme d'inférence de types des que suffisamment d'informations auront pu être rassemblées.
let r = ref []
val r : '_a list ref = {contents = []}
let f = List.map (fun x -> x)
val f : '_a list -> '_a list = <fun>
L'expression fautive ne pouvant pas être compilée en l'état, il faut envisager deux cas :
  • L'expression ne peut pas être rendue polymorphe, comme dans le cas de r au-dessus. L'utilisateur doit utiliser une annotation de type explicite pour en faire une expression complètement monomorphe.
  • L'expression peut être rendue polymorphe en réécrivant certaines parties du code (par exemple en utilisant l'eta-expansion), comme dans le cas de f.
Comment écrire une fonction qui reçoit un argument polymorphe ?
En ML, l'argument d'une fonction ne peut pas être polymorphe à l'intérieur du corps de la fonction, ce qui explique le typage suivant :
let f (g : 'a -> 'a) x y = g x, g y
val f : ('a -> 'a) -> 'a -> 'a -> 'a * 'a = <fun>
La fonction n'est pas aussi polymorphe qu'on aurait pu l'espérer.
En OCaml il est néanmoins possible d'utiliser du polymorphisme de premier ordre. Pour cela on peut utiliser des enregistrements ou des objets; dans le cas des enregistrements il faut déclarer le type avant de l'utiliser dans la fonction.
let f (o : <g : 'a. 'a -> 'a>) x y = o#g x, o#g y
val f : < g : 'a. 'a -> 'a > -> 'b -> 'c -> 'b * 'c = <fun>
type id = { g : 'a. 'a -> 'a; }
type id = { g : 'a. 'a -> 'a; }
let f r x y = r.g x, r.g y
val f : id -> 'a -> 'b -> 'a * 'b = <fun>

Entrées-sorties

Pourquoi certaines sorties ne sont-elles pas dans le bon ordre ?
Si vous utilisez le module format, vous ne devez pas mélanger les ordres d'impression de format avec les ordres d'impression habituels, car les ordres d'impression de format sont retardées pour faire la mise en page et les coupures de lignes, alors que les impressions habituelles ne sont pas soumises à ce retard.
print_string "before";
Format.print_string "MIDDLE";
print_string "after";;
beforeafterMIDDLE- : unit = ()
Pour éviter ce problème, il ne faut pas mélanger les ordres d'impression de format et les ordres de plus bas niveau; c'est pourquoi on a l'habitude d'ouvrir le module format lorsqu'on utilise ses fonctions.