Exercice imposé, écrivons l'exemple des carrés magiques en Caml:
#open "printf";;
let magique a =
let n = vect_length a in
let i = ref (n - 1) in
let j = ref (n / 2) in
for k = 1 to n * n do
a.(!i).(!j) <- k;
if k mod n = 0 then decr i else
begin
i := (!i + 1) mod n;
j := (!j + 1) mod n;
end
done;;
let erreur s = printf "Erreur fatale: %s\\n" s; exit 1;;
let lire () =
printf "Taille du carré magique, svp ? ";
let n = int_of_string (read_line ()) in
if n <= 0 || n mod 2 = 0 then erreur "Taille impossible" else n;;
let imprimer a =
for i = 0 to vect_length a - 1 do
for j = 0 to vect_length a - 1 do
printf "%4d " a.(i).(j)
done;
printf "\\n"
done;;
let main () =
let n = lire () in
let a = make_matrix n n 0 in
magique a;
imprimer a;
exit 0;;
main ();;
Phrases
On constate qu'un programme Caml est une suite de phrases qui se terminent toutes par ;;. Ces phrases sont des définitions de valeurs, de procédures ou de fonctions, ou encore des expressions qui sont évaluées dans l'ordre de présentation. Ainsi, la dernière phrase est l'expression main ();; qui déclenche l'exécution du programme. On remarque aussi que les définitions des objets précédent toujours leur première utilisation.
Une définition est introduite par le mot clé let suivi du nom de l'entité définie. Par exemple, let n = vect_length a définit la variable n comme la longueur du vecteur a et let magique a = ... définit la fonction magique avec a pour argument. À l'occasion, on remarque qu'en Caml on évite les parenthèses inutiles (mais le langage admet les parenthèses superflues); ainsi, l'application des fonctions est notée par simple juxtaposition, et l'on écrit vect_length a plutôt que vect_length (a).
La valeur des variables en Caml est fixée une fois pour toutes lors de leur définition et cette liaison n'est pas modifiable (il est impossible de changer la valeur liée à un nom défini par let). Comme en mathématiques, les variables sont des noms liés à des constantes qu'on calcule lors de la définition de ce nom. C'est aussi analogue aux constantes de Pascal, si ce n'est que l'expression liée à une variable Caml est quelconque et qu'il n'y a pas de limite à sa complexité.
Les variables de Caml ne sont donc pas des variables au sens traditionnel des langages de programmation, puisqu'il est impossible de modifier leur valeur. Il est pourtant souvent nécessaire d'utiliser dans les programmes des variables modifiables au sens de Pascal ou de C. En Caml, on utilise pour cela une référence modifiable vers une valeur, c'est-à-dire une case mémoire dont on peut lire et écrire le contenu. Pour créer une référence, on applique le constructeur ref au contenu initial de la case mémoire. C'est le cas pour la variable i, définie par la ligne let i = ref (n - 1), dont la valeur est une référence qui contient n - 1 à la création. Pour lire le contenu d'une référence, on utilise l'opérateur !, qu'on lit ``contenu de'' (ou ``deref'' car on l'appelle aussi opérateur de déréférencement). Pour écrire le contenu d'une référence on utilise l'opérateur d'affectation :=. Par exemple, i := !i + 1 incrémente le contenu de la référence de la variable i, ce qui a finalement le même effet que l'affectation i := i + 1 de Pascal ou l'affectation i = i + 1 de C. Noter que les références ne contredisent pas le dogme ``une variable est toujours liée à la même valeur'': la variable i est liée à une unique référence et il est impossible de la changer. Plus précisément, la valeur de i est l'adresse de la case mémoire modifiable qui contient la valeur, et cette adresse est une constante. On ne peut que modifier le contenu de l'adresse.
Le connaisseur de Pascal ou de C est souvent troublé par cette distinction explicite entre une référence et son contenu qui oblige à appliquer systématiquement l'opérateur ! pour obtenir le contenu d'une référence, alors que ce déréférencement est implicite en Pascal et en C. En Caml, quand i a été défini comme une référence, la valeur de i est la référence elle-même et jamais son contenu: pour obtenir le contenu, il faut appliquer une opération de déréférencement explicite et l'on écrit !i. Sémantiquement, !i est à rapprocher de *i en C ou i^ en Pascal.
L'opérateur d'affectation := doit être rapproché aussi des opérateurs ordinaires dont il a le statut, e1 := e2 signifie que le résultat de l'évaluation de e1 est une référence dont le contenu va devenir la valeur de e2 (de même que e1 + e2 renvoie la somme des valeurs de e1 et e2). Évidemment, dans la grande majorité des cas, la partie gauche de l'affectation est réduite à un identificateur, et l'on affecte simplement la référence qui lui est liée. Ainsi, en écrivant i := !i - 1, on décrémente le contenu de la référence i en y mettant le prédécesseur de son contenu actuel. Cette opération de décrémentation est d'ailleurs prédéfinie sous la forme d'une procédure qui prend une référence en argument et la décrémente:
let decr x =
x := !x - 1;;
Dans cet exemple, la distinction référence-contenu est évidente: l'argument de decr est la référence elle-même, pas son contenu. Cette distinction référence-contenu s'éclaire encore si l'on considère les références comme des vecteurs à une seule case: c'est alors un prolongement naturel de la nécessaire distinction entre un vecteur et le contenu de ses cases.
L'opérateur d'affectation en Caml pose une petite difficulté supplémentaire aux habitués des langages impératifs: comme nous venons de le voir, l'écriture e1 := e2 impose que le résultat de l'évaluation de e1 soit une référence. Pour des raisons de typage, il n'est donc pas question d'utiliser le symbole := pour affecter des cases de vecteurs, ni des caractères de chaînes, ni même des champs d'enregistrement. Chacune de ces opérations possède son propre opérateur (où intervient le symbole <-).
Un vecteur est une succession de cases mémoires. Les indices des éléments commencent en 0, si le vecteur est de longueur n les indices vont de 0 à n - 1. Pour accéder aux éléments d'un vecteur v, on utilise la notation v.(indice). Pour modifier le contenu des cases de vecteur, on utilise le symbole <- qu'on lit reçoit. Par exemple v.(i) <- k met la valeur k dans la case i du vecteur v.
Pour créer un tableau, on appelle la primitive make_matrix. La ligne
let c = make_matrix n n 0 in
définit donc une matrice n × n, dont les éléments sont des entiers, tous initialisés à 0. Chaque élément de la matrice c ainsi définie est accédé par la notation c.(i).(j), et modifié par la notation c.(i).(j) <- nouvelle valeur. Comme la notation le suggère, c.(i).(j) signifie en fait (c.(i)).(j), c'est-à-dire accès au j[sup][size=9]ième[/size][/sup] élément du vecteur c.(i). Cela veut dire que la matrice est en réalité un vecteur dont les éléments sont eux-mêmes des vecteurs: les lignes de la matrice. (Mais rien n'empêche évidemment de définir une matrice comme le vecteur de ses colonnes.) Retenons qu'en Caml comme en C, les tableaux sont des vecteurs de vecteurs. D'autre part, la ligne let c = make_matrix n n 0 in définit un tableau dont la taille n'est pas une constante connue à la compilation, mais une valeur déterminée à l'exécution (ici n est lue sur l'entrée standard); cependant, une fois le tableau créé, sa taille est fixée une fois pour toutes et n'est plus modifiable.
Caml est un langage fonctionnel: comme nous l'avons déjà vu, les fonctions forment les briques de base des programmes. En outre, les fonctions sont des valeurs primitives du langage qu'on manipule au même titre que les autres valeurs. Il est très facile de définir des fonctions qui manipulent des fonctions ou même de fabriquer des structures de données qui comportent des fonctions. Une fonction peut librement être prise en argument ou rendue en résultat, et il n'y a pas de restriction à son usage dans les structures de données.
Voici un exemple de fonction des entiers dans les entiers:
let prochain x = if x mod 2 = 1 then 3 * x + 1 else x / 2;;
La fonction prochain renvoie 3x+1 si x est impair, et ë x/2 û si x est pair. (On peut s'amuser à itérer cette fonction et à observer ses résultats successifs; on ne sait toujours pas démontrer qu'on obtient finalement 1, quelque soit l'entier de départ. Par exemple: 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1).
Définissons maintenant le prédicat even, qui teste la parité des entiers (c'est une fonction des entiers dans les booléens):
let even x = x mod 2 = 0;;
On remarque que les définitions de prochain et de even ne font pas intervenir de types: la signature des fonctions est implicite. Pourtant Caml dispose, comme Pascal, d'un typage fort, c'est-à-dire strict et vérifié complètement à la compilation. En effet, les types des arguments et du résultat des fonctions sont automatiquement calculés par le compilateur, sans intervention du programmeur, ni annotations de type dans les programmes. L'habitué de Pascal ou C pourra s'il le désire insérer des types dans ses programmes, à l'aide de contraintes de type. Une contrainte de type consiste en un morceau de programme mis entre parenthèses et décoré avec son type. Ainsi (x : int) impose que x soit un entier. Avec une contrainte sur son argument et son résultat la fonction even s'écrirait:
let even (x : int) = (x mod 2 = 0 : bool);;
Comme on le constate sur le programme du carré magique, les contraintes de type ne sont pas nécessaires, il n'est donc pas d'usage d'en mettre dans les programmes.
La fonction magique est elle aussi une procédure, elle construit un carré magique d'ordre n impair de la même façon qu'en Pascal ou C. La fonction lire lit la taille du carré magique et fait quelques vérifications sur sa valeur. En cas de taille incorrecte, elle appelle la fonction erreur qui affiche un message et arrête le programme en erreur. Pour lire cette taille, elle appelle la procédure de lecture d'une ligne read_line, après avoir imprimé un message sur le terminal. La procédure d'impression formatée printf a pour premier paramètre une chaîne de caractères, délimitée par des guillemets. C'est le format qui indique comment imprimer les arguments qui suivent: on spécifie le type d'impression désiré, à l'aide de caractères symboliques, précédés de l'indicateur %. Par exemple %s signifie qu'on doit imprimer une chaîne de caractères, et %d un entier. L'ordre des indications d'impression dans le format doit être corrélé avec l'ordre des arguments à imprimer. Dans la procédure imprimer qui imprime le tableau a, l'indication %4d indique l'impression d'un entier sur 4 caractères, cadré à droite.
Enfin, la procédure main est la procédure principale du programme qui fait appel successivement aux différentes procédures, dans un ordre simple et compréhensible. La méthode qui consiste à définir de petites fonctions, qu'on appelle ensuite dans la fonction principale est un principe de programmation structurée. Elle améliore la lisibilité et facilite les modifications, mais n'est pas une obligation du langage. Nous aurions pu définir toutes les fonctions locales à la fonction main, mais en général ce style est mauvais, car on mélange le coeur algorithmique du programme (la procédure magique) avec des détails annexes comme l'impression et la lecture. Remarquons qu'il faut appeler explicitement le programme principal, ce que fait la ligne main ();; À défaut, la procédure main serait définie mais pas appelée, et le programme ne ferait rien.
B.2 Quelques éléments de Caml
Appel au compilateur
Sur la plupart des machines, le système Caml Light propose un compilateur indépendant camlc qui produit de façon traditionnelle un programme exécutable à partir d'un programme source, contenu dans un fichier ayant l'extension .ml. On précise le nom de l'exécutable produit en donnant l'option -o nom_de_programme au compilateur; à défaut, il produit le programme a.out. Voici un exemple de compilation obtenue sous le système Unix. poly% cat fact.ml
let rec fact x = if x <= 1 then 1 else x * fact (x - 1);;
print_int (fact 10); print_newline ();;
poly% camlc fact.ml
poly% a.out
3628800
En dehors du compilateur proprement dit, les systèmes Caml offrent une interface interactive de dialogue avec le compilateur. Dans cette interface, on entre successivement des phrases qui sont compilées, puis exécutées au vol. Voici une session obtenue sur une machine Unix en lançant le système interactif par la commande camllight.
poly% camllight
> Caml Light version 0.71
#let rec fact x = if x <= 1 then 1 else x * fact (x - 1);;
fact : int -> int =
#fact 10;;
- : int = 3628800
#quit();;
poly%
Dans cette utilisation, Caml indique le résultat des calculs et le type des objets définis. Il trouve automatiquement ces types par un algorithme d'inférence de type. Au lieu de taper directement les programmes, on peut charger les fichiers qui les contiennent par la fonction include. Les phrases du fichier sont alors compilées et exécutées à la volée, exactement comme si on les avait tapées dans le système interactif. Ainsi, le chargement du fichier fact.ml exécute les phrases dans l'ordre la fin du programme. On peut alors reprendre l'interaction comme avant le chargement:
#include "fact.ml";;
fact : int -> int =
3628800
- : unit = ()
#
#open "printf";;
let magique a =
let n = vect_length a in
let i = ref (n - 1) in
let j = ref (n / 2) in
for k = 1 to n * n do
a.(!i).(!j) <- k;
if k mod n = 0 then decr i else
begin
i := (!i + 1) mod n;
j := (!j + 1) mod n;
end
done;;
let erreur s = printf "Erreur fatale: %s\\n" s; exit 1;;
let lire () =
printf "Taille du carré magique, svp ? ";
let n = int_of_string (read_line ()) in
if n <= 0 || n mod 2 = 0 then erreur "Taille impossible" else n;;
let imprimer a =
for i = 0 to vect_length a - 1 do
for j = 0 to vect_length a - 1 do
printf "%4d " a.(i).(j)
done;
printf "\\n"
done;;
let main () =
let n = lire () in
let a = make_matrix n n 0 in
magique a;
imprimer a;
exit 0;;
main ();;
Phrases
On constate qu'un programme Caml est une suite de phrases qui se terminent toutes par ;;. Ces phrases sont des définitions de valeurs, de procédures ou de fonctions, ou encore des expressions qui sont évaluées dans l'ordre de présentation. Ainsi, la dernière phrase est l'expression main ();; qui déclenche l'exécution du programme. On remarque aussi que les définitions des objets précédent toujours leur première utilisation.
Une définition est introduite par le mot clé let suivi du nom de l'entité définie. Par exemple, let n = vect_length a définit la variable n comme la longueur du vecteur a et let magique a = ... définit la fonction magique avec a pour argument. À l'occasion, on remarque qu'en Caml on évite les parenthèses inutiles (mais le langage admet les parenthèses superflues); ainsi, l'application des fonctions est notée par simple juxtaposition, et l'on écrit vect_length a plutôt que vect_length (a).
La valeur des variables en Caml est fixée une fois pour toutes lors de leur définition et cette liaison n'est pas modifiable (il est impossible de changer la valeur liée à un nom défini par let). Comme en mathématiques, les variables sont des noms liés à des constantes qu'on calcule lors de la définition de ce nom. C'est aussi analogue aux constantes de Pascal, si ce n'est que l'expression liée à une variable Caml est quelconque et qu'il n'y a pas de limite à sa complexité.
Références
En Caml, la valeur d'une variable est fixée lors de sa définition.
Les variables de Caml ne sont donc pas des variables au sens traditionnel des langages de programmation, puisqu'il est impossible de modifier leur valeur. Il est pourtant souvent nécessaire d'utiliser dans les programmes des variables modifiables au sens de Pascal ou de C. En Caml, on utilise pour cela une référence modifiable vers une valeur, c'est-à-dire une case mémoire dont on peut lire et écrire le contenu. Pour créer une référence, on applique le constructeur ref au contenu initial de la case mémoire. C'est le cas pour la variable i, définie par la ligne let i = ref (n - 1), dont la valeur est une référence qui contient n - 1 à la création. Pour lire le contenu d'une référence, on utilise l'opérateur !, qu'on lit ``contenu de'' (ou ``deref'' car on l'appelle aussi opérateur de déréférencement). Pour écrire le contenu d'une référence on utilise l'opérateur d'affectation :=. Par exemple, i := !i + 1 incrémente le contenu de la référence de la variable i, ce qui a finalement le même effet que l'affectation i := i + 1 de Pascal ou l'affectation i = i + 1 de C. Noter que les références ne contredisent pas le dogme ``une variable est toujours liée à la même valeur'': la variable i est liée à une unique référence et il est impossible de la changer. Plus précisément, la valeur de i est l'adresse de la case mémoire modifiable qui contient la valeur, et cette adresse est une constante. On ne peut que modifier le contenu de l'adresse.
Le connaisseur de Pascal ou de C est souvent troublé par cette distinction explicite entre une référence et son contenu qui oblige à appliquer systématiquement l'opérateur ! pour obtenir le contenu d'une référence, alors que ce déréférencement est implicite en Pascal et en C. En Caml, quand i a été défini comme une référence, la valeur de i est la référence elle-même et jamais son contenu: pour obtenir le contenu, il faut appliquer une opération de déréférencement explicite et l'on écrit !i. Sémantiquement, !i est à rapprocher de *i en C ou i^ en Pascal.
L'opérateur d'affectation := doit être rapproché aussi des opérateurs ordinaires dont il a le statut, e1 := e2 signifie que le résultat de l'évaluation de e1 est une référence dont le contenu va devenir la valeur de e2 (de même que e1 + e2 renvoie la somme des valeurs de e1 et e2). Évidemment, dans la grande majorité des cas, la partie gauche de l'affectation est réduite à un identificateur, et l'on affecte simplement la référence qui lui est liée. Ainsi, en écrivant i := !i - 1, on décrémente le contenu de la référence i en y mettant le prédécesseur de son contenu actuel. Cette opération de décrémentation est d'ailleurs prédéfinie sous la forme d'une procédure qui prend une référence en argument et la décrémente:
let decr x =
x := !x - 1;;
Dans cet exemple, la distinction référence-contenu est évidente: l'argument de decr est la référence elle-même, pas son contenu. Cette distinction référence-contenu s'éclaire encore si l'on considère les références comme des vecteurs à une seule case: c'est alors un prolongement naturel de la nécessaire distinction entre un vecteur et le contenu de ses cases.
L'opérateur d'affectation en Caml pose une petite difficulté supplémentaire aux habitués des langages impératifs: comme nous venons de le voir, l'écriture e1 := e2 impose que le résultat de l'évaluation de e1 soit une référence. Pour des raisons de typage, il n'est donc pas question d'utiliser le symbole := pour affecter des cases de vecteurs, ni des caractères de chaînes, ni même des champs d'enregistrement. Chacune de ces opérations possède son propre opérateur (où intervient le symbole <-).
Vecteurs et tableaux
En Caml, l'opérateur := est réservé aux références.
Un vecteur est une succession de cases mémoires. Les indices des éléments commencent en 0, si le vecteur est de longueur n les indices vont de 0 à n - 1. Pour accéder aux éléments d'un vecteur v, on utilise la notation v.(indice). Pour modifier le contenu des cases de vecteur, on utilise le symbole <- qu'on lit reçoit. Par exemple v.(i) <- k met la valeur k dans la case i du vecteur v.
Pour créer un tableau, on appelle la primitive make_matrix. La ligne
let c = make_matrix n n 0 in
définit donc une matrice n × n, dont les éléments sont des entiers, tous initialisés à 0. Chaque élément de la matrice c ainsi définie est accédé par la notation c.(i).(j), et modifié par la notation c.(i).(j) <- nouvelle valeur. Comme la notation le suggère, c.(i).(j) signifie en fait (c.(i)).(j), c'est-à-dire accès au j[sup][size=9]ième[/size][/sup] élément du vecteur c.(i). Cela veut dire que la matrice est en réalité un vecteur dont les éléments sont eux-mêmes des vecteurs: les lignes de la matrice. (Mais rien n'empêche évidemment de définir une matrice comme le vecteur de ses colonnes.) Retenons qu'en Caml comme en C, les tableaux sont des vecteurs de vecteurs. D'autre part, la ligne let c = make_matrix n n 0 in définit un tableau dont la taille n'est pas une constante connue à la compilation, mais une valeur déterminée à l'exécution (ici n est lue sur l'entrée standard); cependant, une fois le tableau créé, sa taille est fixée une fois pour toutes et n'est plus modifiable.
Fonctions et procédures
En Caml, la taille des vecteurs est fixée à la création.
Caml est un langage fonctionnel: comme nous l'avons déjà vu, les fonctions forment les briques de base des programmes. En outre, les fonctions sont des valeurs primitives du langage qu'on manipule au même titre que les autres valeurs. Il est très facile de définir des fonctions qui manipulent des fonctions ou même de fabriquer des structures de données qui comportent des fonctions. Une fonction peut librement être prise en argument ou rendue en résultat, et il n'y a pas de restriction à son usage dans les structures de données.
Comme en mathématiques, une fonction a des arguments et rend un résultat qu'elle calcule avec une expression où intervient la valeur de ses arguments. Comme pour les autres valeurs, la définition d'une fonction est introduite par un mot clé let suivi du nom de la fonction et de la liste de ses arguments, ce qui nous donne typiquement let f x = ... pour une fonction à un argument et let f x1 x2 ... xn = ... pour une fonction à n arguments.
En Caml, les fonctions sont des valeurs comme les autres.
Voici un exemple de fonction des entiers dans les entiers:
let prochain x = if x mod 2 = 1 then 3 * x + 1 else x / 2;;
La fonction prochain renvoie 3x+1 si x est impair, et ë x/2 û si x est pair. (On peut s'amuser à itérer cette fonction et à observer ses résultats successifs; on ne sait toujours pas démontrer qu'on obtient finalement 1, quelque soit l'entier de départ. Par exemple: 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1).
Définissons maintenant le prédicat even, qui teste la parité des entiers (c'est une fonction des entiers dans les booléens):
let even x = x mod 2 = 0;;
On remarque que les définitions de prochain et de even ne font pas intervenir de types: la signature des fonctions est implicite. Pourtant Caml dispose, comme Pascal, d'un typage fort, c'est-à-dire strict et vérifié complètement à la compilation. En effet, les types des arguments et du résultat des fonctions sont automatiquement calculés par le compilateur, sans intervention du programmeur, ni annotations de type dans les programmes. L'habitué de Pascal ou C pourra s'il le désire insérer des types dans ses programmes, à l'aide de contraintes de type. Une contrainte de type consiste en un morceau de programme mis entre parenthèses et décoré avec son type. Ainsi (x : int) impose que x soit un entier. Avec une contrainte sur son argument et son résultat la fonction even s'écrirait:
let even (x : int) = (x mod 2 = 0 : bool);;
Comme on le constate sur le programme du carré magique, les contraintes de type ne sont pas nécessaires, il n'est donc pas d'usage d'en mettre dans les programmes.
Lorsque l'argument ou le résultat d'une fonction sont sans intérêt, on le note alors (), l'unique valeur du type unit. Une telle fonction est souvent qualifiée de procédure au lieu de fonction, mais ce n'est qu'une distinction commode qui n'est pas faite par le langage. Par exemple, main est une procédure, et l'on constate qu'elle est définie exactement de la même manière que notre fonction prochain ou le prédicat even.
En Caml, le typage est à la charge du compilateur.
La fonction magique est elle aussi une procédure, elle construit un carré magique d'ordre n impair de la même façon qu'en Pascal ou C. La fonction lire lit la taille du carré magique et fait quelques vérifications sur sa valeur. En cas de taille incorrecte, elle appelle la fonction erreur qui affiche un message et arrête le programme en erreur. Pour lire cette taille, elle appelle la procédure de lecture d'une ligne read_line, après avoir imprimé un message sur le terminal. La procédure d'impression formatée printf a pour premier paramètre une chaîne de caractères, délimitée par des guillemets. C'est le format qui indique comment imprimer les arguments qui suivent: on spécifie le type d'impression désiré, à l'aide de caractères symboliques, précédés de l'indicateur %. Par exemple %s signifie qu'on doit imprimer une chaîne de caractères, et %d un entier. L'ordre des indications d'impression dans le format doit être corrélé avec l'ordre des arguments à imprimer. Dans la procédure imprimer qui imprime le tableau a, l'indication %4d indique l'impression d'un entier sur 4 caractères, cadré à droite.
Enfin, la procédure main est la procédure principale du programme qui fait appel successivement aux différentes procédures, dans un ordre simple et compréhensible. La méthode qui consiste à définir de petites fonctions, qu'on appelle ensuite dans la fonction principale est un principe de programmation structurée. Elle améliore la lisibilité et facilite les modifications, mais n'est pas une obligation du langage. Nous aurions pu définir toutes les fonctions locales à la fonction main, mais en général ce style est mauvais, car on mélange le coeur algorithmique du programme (la procédure magique) avec des détails annexes comme l'impression et la lecture. Remarquons qu'il faut appeler explicitement le programme principal, ce que fait la ligne main ();; À défaut, la procédure main serait définie mais pas appelée, et le programme ne ferait rien.
B.2 Quelques éléments de Caml
Appel au compilateur
Sur la plupart des machines, le système Caml Light propose un compilateur indépendant camlc qui produit de façon traditionnelle un programme exécutable à partir d'un programme source, contenu dans un fichier ayant l'extension .ml. On précise le nom de l'exécutable produit en donnant l'option -o nom_de_programme au compilateur; à défaut, il produit le programme a.out. Voici un exemple de compilation obtenue sous le système Unix. poly% cat fact.ml
let rec fact x = if x <= 1 then 1 else x * fact (x - 1);;
print_int (fact 10); print_newline ();;
poly% camlc fact.ml
poly% a.out
3628800
En dehors du compilateur proprement dit, les systèmes Caml offrent une interface interactive de dialogue avec le compilateur. Dans cette interface, on entre successivement des phrases qui sont compilées, puis exécutées au vol. Voici une session obtenue sur une machine Unix en lançant le système interactif par la commande camllight.
poly% camllight
> Caml Light version 0.71
#let rec fact x = if x <= 1 then 1 else x * fact (x - 1);;
fact : int -> int =
#fact 10;;
- : int = 3628800
#quit();;
poly%
Dans cette utilisation, Caml indique le résultat des calculs et le type des objets définis. Il trouve automatiquement ces types par un algorithme d'inférence de type. Au lieu de taper directement les programmes, on peut charger les fichiers qui les contiennent par la fonction include. Les phrases du fichier sont alors compilées et exécutées à la volée, exactement comme si on les avait tapées dans le système interactif. Ainsi, le chargement du fichier fact.ml exécute les phrases dans l'ordre la fin du programme. On peut alors reprendre l'interaction comme avant le chargement:
#include "fact.ml";;
fact : int -> int =
3628800
- : unit = ()
#