L'oubli du second paramètre de Array.make
induit logiquement une erreur. Supposons ainsi qu'on souhaite créer un tableau d'entiers de taille 25, mais qu'on oublie la valeur du contenu initial à mettre dans les cases :
let tab = Array.make 25 in print_int tab.(2);
File "test.ml", line 2, characters 10-13: This expression has type 'a -> 'a array but is here used with type 'b array
Pour des raisons qu'on comprendra bien plus tard, il n'y a pas d'erreur au moment de la déclaration du tableau. L'erreur ne se situe que lors de son utilisation. Ainsi le nom tab
ligne 2 pose un problème. Ce nom devrait être de type 'b array
ce qui veut dire "tableau d'un type quelconque"
. Pourtant le type de tab
est 'a -> 'a array
: le fait d'avoir oublié un paramètre à la fonction Array.make
fait que tab
a été associé à une fonction ! C'est assez déroutant comme idée pour l'instant, mais cela ne nous empêche pas de repérer le problème. Vu que l'erreur est sur tab
et que la ligne 2 semble correcte, c'est qu'il faut aller chercher une erreur sur la définition de tab
. On peut alors s'apercevoir d'un oubli du second paramètre de Array.make
.
Qu'est-ce que c'est qu'un tableau de taille zéro ? Serait-ce une aberration ? Non, c'est simplement un tableau qui ne contient aucun élément. Quel est l'intérêt d'avoir un tableau vide ? Ca peut très bien arriver dans des cas pratique. Supposons par exemple que l'on utilise un tableau pour stocker les coordonnées des contacts de son carnet d'adresse. Tout au début, il n'y a encore aucun nom dans le carnet. Le tableau associé à ce carnet tout neuf est donc un tableau de taille zéro. Remarque : pour déclarer un tableau vide, on fait par exemple let t = [||] in...
Que se passe-t-il alors si l'on essaie de fabriquer un tableau de taille négative ? Essayons avec Array.make
de créer un tableau de taille -3
, rempli de la chaîne "test"
.
let t = Array.make (-3) "test" in print_string t.(0);
On n'obtient pas d'erreur de compilation, l'erreur ne sera détectée qu'au moment de l'exécution :
Fatal error: exception Invalid_argument("Array.make")
- Une
"exception"
signifie qu'une erreur s'est produite lors d'un appel de fonction. "Fatal error"
, traduire"Erreur fatale"
, veut dire que le programme a été obligé de s'arrêter brutalement au moment où l'erreur est apparue, car il ne pouvait pas aller plus loin. En effet, il s'est trouvé dans l'incapacité de créer un tableau de taille-3
(cela n'aurait pas de sens)."Invalid_argument"
, traduire"Argument invalide"
signifie que c'est un des paramètres fournis lors de l'appel à la fonction qui a été à l'origine du problème."Array.make"
donne précisément le nom de la fonction qui est à l'origine de l'erreur.
Il est important de remarquer qu'aucune indication sur la ligne du programme où se situe l'erreur ne vous est donnée. Cela est dû au fait que les numéros de lignes ne sont donnés que pour les erreurs de compilation. Pour savoir à quelle ligne est apparue une erreur d'exécution, il faut utiliser un outil spécialisé supplémentaire, appelé "déboggeur"
.
Le recours à cet outil n'est nécessaire que pour des grands projets de plusieurs milliers de lignes, lorsque le code n'a pas été assez soigné lors de l'écriture. Pour des petits programmes, il est facile de localiser une erreur à l'aide d'un simple coup d'œil au code. Pour les programmes de taille moyenne, de quelques centaines de lignes, nous donnerons à la fin du cours une technique rapide et efficace pour localiser l'erreur.
Pourquoi le compilateur n'est-il pas capable de détecter que le paramètre -3
va provoquer une erreur ? Simplement parce que dans la plupart des cas il n'est pas possible de prévoir. Que penser du code suivant ?
let taille = read_int() in let tableau = Array.make taille "test" in print_string "c'est fini";
C'est un programme qui terminera correctement que si l'utilisateur fourni un entier positif ou nul. Dans les autres cas une erreur se produira avant la fin.
Voyons maintenant ce qui se passe si on déclare un tableau let tab = [| 3; 5; -7|] in ...
et qu'on essaie d'accéder à tab.(-2)
ou tab.(3)
, c'est-à-dire à des cases du tableau qui n'existent pas.
let tab = [| 3; 5; -7|] in print_int tab.(-2);
Fatal error: exception Invalid_argument("Array.get")
Ce message nous dit qu'il y a eu une erreur fatale à cause d'une exception de type "Invalid_argument"
, c'est-à-dire d'une erreur générée par un argument invalide dans un appel à une fonction nommée "Array.get"
, traduire "Tableau.récupère"
. Quel peut bien être cette fonction Array.get
qu'on n'a même pas utilisé mais qui provoque pourtant une erreur ?
L'explication est très simple. La notation tab.(i)
où tab
est un tableau et i
est un indice n'est qu'un raccourci pour l'expression : " Array.get tab i "
. Cet appel de fonction permet exactement de récupérer dans le tableau tab
l'élément à l'indice i
. On peut l'utiliser dans son code, c'est juste beaucoup plus lourd que d'écrire simplement tab.(i)
. Ainsi, les deux lignes suivantes sont totalement équivalentes :
print_int (Array.get tab 2); print_int tab.(2);
Le même principe s'applique pour la modification d'une valeur à un indice invalide :
let tab = [| 3; 5; -7|] in tab.(4) <- 12;
Sauf que cette fois l'erreur est sur une fonction nommée "Array.set"
:
Fatal error: exception Invalid_argument("Array.set")
La fonction "Array.set"
, traduire "Tableau.modifier"
, permet de changer le contenu d'une case d'un tableau donné à un indice donné, en précisant la nouvelle valeur lors de l'appel. Ainsi les deux expressions suivantes sont équivalentes :
Array.set tab i valeur; tab.(i) <- valeur;
"Array.length"
, traduire "Tableau.taille"
est une fonction permettant de récupérer la taille du tableau qu'on lui donne en argument. Pour obtenir la taille d'un tableau nommée tab
, on fait donc simplement "Array.length tab"
. Un exemple qui affiche la taille, égale à 3, du tableau :
let tab = [| 3; 5; -7|] in print_int (Array.length tab);
Remarque : comme la taille d'un tableau est toujours positive ou nulle, le résultat d'un appel à Array.length
n'est jamais négatif.
Maintenant que l'on sait récupérer la taille d'un tableau, on va pouvoir réaliser des traitements sur l'ensemble des éléments d'un tableau.
Essayons d'abord d'afficher tous les éléments d'un tableau d'entiers, dans l'ordre et séparés par des espaces. On a déjà vu comment faire :
let tab = [| 4; 9; 8; -5 |] in print_int tab.(0); print_string " "; print_int tab.(1); print_string " "; print_int tab.(2); print_string " "; print_int tab.(3); print_string " ";
Bien évidement, un tel code est inacceptable : il faut utiliser une boucle for
. Ainsi le code se condense en :
let tab = [| 4; 9; 8; -5 |] in for indice = 0 to 3 do print_int tab.(indice); print_string " "; done;
Remarquez que le tableau est de taille 4, et que la boucle for
commence à 0, le premier indice, et se termine à 3, le dernier indice.