On veut écrire un programme qui demande n
nombres à l'utilisateur, et en affiche la somme. Lorsque n
est petit, on sait comment faire : lire tous les nombres avec des let
, calculer alors la somme, et enfin afficher la valeur de cette somme. Mais si n
est trop grand, on ne peut pas procéder ainsi. La solution consiste à ajouter successivement les valeurs données à un total
, et d'afficher simplement le total à la fin :
soit total = 0; faire n fois : demander un entier à l'utilisateur; ajouter cet entier au total; ; afficher total;
Pour l'instant on n'est pas capable de coder ça. Pourquoi ?
Il n'est pas possible de modifier la valeur de total
. Si on fait let total = 0 in
au début du programme, total
sera toujours associé à 0 dans la suite, et donc afficher total
ne pourra afficher que 0.
On avait dit qu'on ne souhaitait pas que la valeur associée à un nom change. Ce souhait est toujours d'actualité, et on va arriver à résoudre le problème sans transgresser à cette règle. Voici comment.
On va simplement dire que total
est le nom d'une boite. Du début, jusqu'à la fin. Ainsi le nom total
reste toujours associé à la même valeur : cette valeur, c'est une boite. La subtilité, c'est qu'on va alors pouvoir changer le contenu de la boite. Le changer comme on veut, quand on veut, où on veut.
Réécrivons donc ce que l'on veut faire, en utilisant une boite, nommée total
. Au début, on crée cette boite, et on met la valeur 0 dedans : 0 est le contenu initial de la boite. Ensuite, à chaque fois que l'utilisateur donne un nombre, on effectue l'opération suivante :
- lire le contenu de la boite
total
, - faire la somme de cette valeur et de l'entier donné par l'utilisateur,
- mettre cette somme dans la boite, à la place de ce qu'il y avait avant.
Version pseudo-code :
soit une nouvelle boite nommée total, avec 0 pour contenu faire n fois : demander un entier à l'utilisateur; mettre dans la boite total : son contenu courant + l'entier donné; ; afficher le contenu de la boite total;
Avant de spécifier la syntaxe permettant de manipuler les boites, nous allons faire deux remarques fondamentales.
1) Une boite est fabriquée pour contenir un certain type d'objets. Elle ne pourra en aucun cas contenir des objets d'un autre type. Ainsi, on distinguera les "boites à entiers"
des "boites à réels"
, et des "boites à texte"
, qui sont des objets de natures distinctes (on dira plus tard de types distincts).
2) Une boite n'est jamais vide. Il faut toujours mettre un contenu dans une boite, même si ce contenu n'est pas intéressant. Ainsi, dès la création d'une boite, il faudra donner un contenu, dit "contenu initial"
.
Résumons :
Pour créer une boite, il faut donner un contenu initial. A tout instant, la boite contiendra un et un seul objet du même type que ce contenu initial.
Cette règle est relativement artificielle. L'objectif est surtout d'empêcher le programmeur de faire des erreurs. L'empêcher de mettre n'importe quel type de contenu dans n'importe quel boite, ou encore l'empêcher de demander le contenu d'une boite qui serait vide. L'intérêt est que si le programmeur se trompe, il sera prévenu au moment de la compilation, et il ne découvrira pas ce problème en utilisant son programme.
D'abord un petit point vocabulaire : techniquement parlant, on utilise le mot "référence"
plutôt que "boite"
. De ce terme vient l'abréviation ref
, qui va nous servir pour construire des références. Une boite conçue pour contenir des entiers sera donc appelée une "référence sur un entier"
. Par ailleurs, lorsqu'on change le contenu d'une référence, on dit que l'on "affecte"
la nouvelle valeur à cette référence.
Voyons maintenant comment manipuler les références.
Pour créer une référence, on écrit le mot ref
, suivi du contenu initial. Par exemple pour une référence contenant l'entier 15, on fait ref 15
. Ensuite pour nommer cette référence, on utilise un let :
let b = ref 15 in
Pour obtenir le contenu d'une référence, on met un point d'exclamation devant son nom : !nom
. Certaines personnes lisent le point d'exclamation : "bang"
, et ainsi !a
se lit "bang a"
. Si on veut afficher le contenu de la boite précédente, on fait donc print_int !b
. Ce qui donne au total :
let b = ref 15 in print_int !b;
Remarque : il n'y a pas besoin de parenthèses autour de !b
après le print_int
, car l'opérateur !
est prioritaire.
Pour affecter un nouveau contenu à une référence, on écrit son nom, puis le symbole :=
, et enfin son nouveau contenu. Changeons ainsi le contenu de b
en le faisant passer de 15 à 18, et affichons ensuite la valeur du nouveau contenu :
let b = ref 15 in print_int !b; b := 18; print_int !b;
Il est extrêmement important de comprendre le principe des références, car ces principes s'étendront par la suite à d'autres structures fondamentales.
Résumé des manipulations de références :
let nom = ref contenu in
pour construire une nouvelle référence.!nom
pour accéder au contenu de la référence.nom := nouveau_contenu
pour affecter un nouveau contenu à la référence.
Le point d'exclamation sert à différencier le nom d'une boite de son contenu. Ainsi, b
est une boite et !b
est son contenu. L'oubli d'un point d'exclamation entraîne une erreur :
let b = ref 15 in print_int b;
File "test.ml", line 2, characters 10-11: This expression has type int ref but is here used with type int
Cette erreur est située sur le b
après le print_int
. Le compilateur nous dit que ce b
a pour type "int ref"
, traduire "référence sur un entier"
. Mais on devrait avoir un "int"
, un entier, puisque print_int
affiche des entiers.
L'oubli d'un ref
:
let b = 15 in b := 18; print_int !b;
File "test.ml", line 2, characters 0-1: This expression has type int but is here used with type 'a ref
L'erreur se situe sur le b
de b := 18
. Ce b
est pour l'instant un entier, qu'on vient de poser égal à 15. Il est pourtant utilisé comme une 'a ref
. Qu'est-ce que cela veut dire ? Simplement "référence sur quelque chose"
. Les 'a
, 'b
et autres lettres précédées d'un prime veulent dire "un type indéterminé"
.
On peut se demander pourquoi le compilateur n'a pas vu qu'on essayait de mettre 18 dans b
, et pourquoi il n'a donc pas dit qu'on utilisait ici b
comme int ref
. La raison est la suivante : il ne s'est même pas donné la peine d'aller lire jusqu'au 18. Au moment où il a vu b :=
il signale déjà l'erreur, et s'arrête tout de suite après. En effet, l'opérateur d'affectation :=
doit obligatoirement être précédé du nom d'une référence.
Enfin l'écriture d'un simple signe =
à la place d'un :=
provoque une erreur de type :
let b = ref 15 in b = 18; print_int !b;
File "test.ml", line 2, characters 4-6: This expression has type int but is here used with type int ref
Souvenez-vous : la seule fois qu'on a utilisé un signe égal en dehors d'un let
c'était pour réaliser un test d'égalité dans une structure if
, du genre if x = 3 then ...
. Comme on le précisera plus tard, il est possible de réaliser des comparaisons aussi en dehors des structures if
. C'est ce qui se passe ici : le compilateur pense que l'on essaie de tester si b
et 18
sont égaux. Ce qui provoque un problème de type, vu que cela n'a pas de sens de regarder si une boite et un entier ont même valeur.