Des actualités personnelles sous un style impersonnel, et inversement.
Follow @Thomas_Jannaud
Vous pouvez vous procurer CamL sur le site de l'INRIA, qui a inventé ce langage qui appartient à la famille des langages fonctionnels (par exemple, ce n'est pas un langage objet). Cela signifie que les boucles for ... pourraient très bien ne pas exister dans ce langage car on pourrait les remplacer, par exemple, par des fonctions récursives avec un test à l'entrée.
Il a été écrit, à la base, pour faciliter la création de compilateurs. C'est un langage très poussé, mais très très propre : c'est très agréable de coder avec, même si les débuts sont parfois difficiles. En effet, le compilateur est extrêmement exigeant. Cela veut aussi dire que si le compilateur compile, il y a de grandes chances pour que le programme n'ait pas d'erreurs. Et ça, c'est bien.
Pour ces raisons, le langage CamL est sans doute celui que je préfère. Je parle du langage en lui-même, pas de l'environnement dans lequel il s'exécute, bien entendu (une console). Mon rêve serait de pouvoir utiliser ce langage dans un environnement de type Visual Basic.
Vous trouverez dans cet ouvrage de quoi apprendre caml par l'exemple... sans doute le meilleur moyen pour appréhender ce langage.
D'autre part, par expérience, vous verrez qu'un site internet ne remplacera jamais un livre papier.
Cette version est en français.
Je ne saurais aussi que trop vous conseiller d'aller voir ma page de codes sources, où je mets pas mal de codes sources en ligne, ce qui peut être très utile pour avoir une vision d'ensemble de la physionomie d'un programme en CamL. En effet, c'est une approche totalement différente de la programmation habituelle, où passer des fonctions en paramètre est chose commune, pour un code clair et compact au final.
D'autre part, au début on ne comprend jamais où mettre les points-virgules dans le code (parfois 0, parfois 1, parfois 2 à la suite).
Regarder des programmes sans essayer de comprendre ce qu'ils font peut vous aider à assimiler cette notion difficile.
Voici par ailleurs un excellent tutoriel pour débuter doucement en CamL.
Il y a deux manières d'utiliser CamL (ou OCamL) : écrire des fonctions dans l'interpréteur, une par une, sans vraiment avoir tout le code sous ses yeux, ou bien écrire son code dans un document texte, l'enregistrer, pouvoir éditer et compiler. Beaucoup d'éditeurs sont justement faits spécialement pour ce genre de chose (coloration syntaxique, compilation et exécution du code de manière simple, ...).
Je vous conseille (roulement de tambour) la seconde solution ! Préférez SublimeText, Atom, Emacs, Notepad++, EditPlus... à un éditeur de texte basique. Ce genre d'éditeurs foisonne sur le Net. Voici les lignes de commandes utilisées pour la compilation et l'exécution :
compilation : ($(FileName) et $(FileDir) sont propres à EditPlus, mais j'imagine que tous les éditeurs ont bien leur petit équivalent
Command="C:\\Program Files\\Objective Caml\\Bin\\ocamlc.exe"
Argument=-I "c:\\program files\\objective caml\\lib" -o output.exe $(FileName)
InitDir=$(FileDir)
exécution :
Command="c:\\program files\\objective caml\\bin\\ocamlrun.exe"
Argument="$(FileDir)\\output.exe"
InitDir=$(FileDir)
Remarque pour la ligne 'Argument' de l'exécution : on peut écrire aussi cela
Argument="$(FileDir)\\output.exe" < input.txt
De cette manière, si le programme demande à l'utilisateur de rentrer un nombre, puis une ligne de caractères, puis... et bien il ira chercher ces renseignements dans le fichier "input.txt" (dans le même dossier que notre code source) plutôt que de demander une saisie dans la console. C'est particulièrement pratique dans le cas où l'on veut tester notre programme sur une grosse entrée (exemple : une matrice, un graphe, un labyrinthe, ...) pour pas avoir à tout retaper à chaque fois.
;
pour ceux qui ne comprennent pas le français),
sauf si le mot qui suit la ligne est 'done' (mot-clé indiquant la fin d'une boucle).;;
, (sauf si elles sont définies dans le corps d'une autre fonction, un peu comme si
elles étaient des variables), ainsi que les déclarations de type ou de variables globales.let x = 3 in
...
ou
let x = 3 and y = 4 and z = 2 in
...
ou
let x = 3;;
...
pour les variables globales (il faut alors les mettre en tout début du fichier)
Attention !!!! on ne pourra jamais modifier la valeur d'une variable ainsi déclarée : ce sont des constantes. Cela peut paraître bizarre que la déclaration par défaut soit la constante, mais on se rend vite compte que c'est l'essentiel d'un code au final.
let x = ref 3 in
incr x; (* incrémentation de notre variable ; decr pour décrémenter *)
x := !x * 2;
(* on a fait (3 + 1) * 2 *)
Remarquez les symboles d'affectation :=
et de "récupération de la valeur" !
.Vous l'avez donc compris, en faisant ref, on déclare en fait une sorte de pointeur vers une variable. CamL repère grâce à ces signes si l'on
ne cherche pas sans faire exprès à affecter une nouvelle valeur à une constante, par exemple. Comme on peut même passer un pointeur en argument
d'une fonction, cela permet d'être sur que l'on ne fera pas n'importe quoi avec, en particulier à faire pointer notre pointeur vers une autre variable
que celle originelle, puisque le pointeur est déclaré comme un pointeur CONSTANT vers une référence.
for k = debut to fin do
...;
...
done;
Il n'y a pas possibilité de faire aller k de 2 en 2 directement, c'est à nous d'utiliser 2*k
. On peut quand même faire for k = fin downto debut do
si l'on veut que les valeurs aillent décroissant.
while condition do (* pas besoin de mettre de parenthèses autour de la condition *)
...
done;
if condition then ...;
ou bien
if condition then
...;
ou bien (cas de plusieurs choses à faire)
if condition then (
...;
...;
...); (* La parenthèse tient ici lieu d'accolade en C++ ou en Java *)
let a = 1. in>. int_of_float ou
float_of_int pour passer d'un int à un float.let [rec] f x y z = (*si la fonction est destinée à être récursive *)
x + y + z;
print_newline();;
Le programme voit qu'à x+y+z il n'y a pas vraiment d'instruction, juste une valeur donnée en vrac. Il la prend en tant que telle comme valeur
de retour de la fonction. Il n'y a donc pas de mot clé return ni rien d'autre. Par conséquent la ligne print_newline()
ne sera jamais exécutée.
Attention ! Bien que vous voyez que l'on ne donne pas de type à notre fonction, il faut absolument dans tous les cas retourner toujours la même chose. Ceci est par conséquent interdit :
let f x =
if x > 2 then
(* CamL comprend ici que x doit être un entier et à la ligne d'après
que la fonction renvoie un entier, donc f: int -> int *)
3
else
"toto";; (* erreur de compilation : on doit renvoyer un int, pas une chaîne *)
Remarque : si l'on a effectivement besoin d'une fonction qui renvoie dans un cas un entier, et dans un autre une chaîne, il y a 2 moyens pour arriver à ses fins.
(false, 42, "toto")
(42 étant mis au hasard puisque
de même il ne devrait pas être pris en compte par la suite étant donné le booléen.montype = Entier of int | Chaine of string
,
et renvoyer Entier 3
ou Chaine "toto"
. CamL saura que la fonction renvoie quelque chose du type montype.Fonction ne prenant aucun argument
let f () =
Printf.printf "bonjour, je suis une fonction ne prenant aucun argument !";;
(* Printf.printf : affichage à l'écran d'une phrase *)
Fonction dans une fonction : idem que pour une variable
let f x y z =
let g a = 2 * a + x in
Printf.printf "%d %d" (g y) (g z);; (* %d demande d'afficher un entier *)
Comme vous pouvez le remarquer, un truc sympa en CamL c'est qu'il n'y a pas de parenthèses partout qui brouillent la vue et la compréhension du code
Il y a une autre manière de déclarer une fonction :
let f = fun x y z -> (ou let f = fun () -> pour les fonctions sans argument initial)
...
...;;
L'intérêt de déclarer une fonction dans une fonction n'est peut être pas très clair, surtout au vu des exemples un peu simplistes que je donne, mais dites vous que :
2 * a + x
ne mérite pas à première vue le titre de 'fonction', il y a fort à parier que d'autres morceaux de code ne s'en serviront pas, et donc plutôt que de la déclarer globalement on peut la déclarer localement là où elle va être utilisée.match x with
| 0 -> Printf.printf "bouh"
| 1 -> Printf.printf "ah, c'est un peu mieux"
| _ -> Printf.printf "bravo !"; (* on arrive ici si x vaut ni 0 ni 1 *)
Ça a encore l'air un peu pourri comme ça, mais considérez cet exemple :
match x with
| (0, a) -> Printf.printf "tu perds et on t'as mis %d buts" a
| (1, a) -> Printf.printf "ah, c'est un peu mieux"
| (b, 0) -> Printf.printf "tu lui a mis la pâtée !!"
| (b, c) -> Printf.printf "%d buts en tout" (b + c);
Ici x est un couple d'entiers, (représentant notre score puis celui de l'adversaire, mais ça le compilateur s'en ficher). Quand on fait (0, a), où a n'a encore pas été déclaré plus haut, cela veut dire que CamL doit juste filtrer si notre score est 0, et auquel cas il stocke la seconde valeur du couple dans la variable a, alors disponible pour traitement futur. Vous verrez cette fonction se révèlera indispensable dans le futur, avec les types utilisateurs.
let f = function
| 0 -> Printf.printf "bouh"
| 1 -> Printf.printf "ah, c'est un peu mieux"
| _ -> Printf.printf "bravo !"; (* on arrive ici si x vaut ni 0 ni 1 *);;
Ceci déclare la fonction f et fait directement le match avec l'argument : même pas besoin de le déclarer ; Bien sûr il faut ne pas en avoir
besoin après, de l'argument (et c'est assez fréquent, vous verrez ça avec les listes).let table = Array.make n 0 in...
[| 1; 2; 5; 3; 42 |]
let table = Array.init n (fun i -> f (i)) in
let table = Array.make_matrix hauteur largeur 0 in...
let table = Array.init hauteur (fun i -> Array.make largeur 0) ou plus simplement
let table1 = Array.make largeur 0 in
let table = Array.make hauteur table1 in
for k = 1 to hauteur
table.(k - 1) = Array.make largeur 0
done;;
Les deux premières lignes (après "simplement") servent à indiquer à CamL que l'on a un tableau de taille hauteur, et que ce tableau contient
des tableaux. Le seul problème, c'est que ces tableaux sont tous les mêmes, puisqu'ils sont initialisés avec table1 (qui est un pointeur vers ...).
La preuve, faites table.(0).(0) = 2; Printf.printf "%d\
" table.(1).(0)
et vous verrez que ça fait 2 aussi. On demande donc dans la boucle
for de recréer un certain nombre (hauteur) de nouveaux tableaux (et de nouveaux espaces mémoire). La fonction Array.make (ou init) renvoie un
pointeur vers ce nouvel espace mémoire, pointeur qui est stocké dans table.(i)
Cette méthode permet de créer des tableaux de n'importe quelle dimension. D'autre part, ce n'est pas parce que je dis "matrice" que le tableau à 2 dimensions créé est rectangulaire. Non, c'est juste un tableau de tableaux, dont certains peuvent être de taille 0 ou 1 ou 10 et d'autres de taille 55. D'où les méthodes qui suivent.
Array.length table
let nouveau = Array.sub t i n
Une chaîne est un tableau de caractères. A ce titre, presque tout est pareil que pour les tableaux, à quelques exceptions :
let machaine = String.make n 'a' in
... ou bien let machaine = "toto" in...
machaine.[n]
au lieu de tableau.(n)
String.length machaine
et String.sub
aussiPrintf.printf "%s" machaine
pour afficher une chaîne (ou print_string machaine
)machaine ^ uneautrechaine
pour concaténer deux chaînesElles sont prédominantes en CamL, qui utilise beaucoup les fonctions récursives, et donc les listes dans les arguments. Attention, les éléments des listes ne sont pas modifiables !!! (sauf les listes de tableaux, de pointeurs sur des entiers, et de chaines de caractères (qui sont des tableaux), car les tableaux sont des pointeurs, donc on ne modifie pas le pointeur lui-même (il reste constant) mais là vers où il pointe).
L'habitude est donc en CamL de refaire toute une liste quand on veut juste changer un élément, ... Ça a l'air énervant comme ça, mais il n'y a pas souvent de choses à changer en pratique, et d'autre part s'il faut le faire, utilisez des pointeurs.
let listevide = [] in...
ou let listepleine = ["toto"; "tata"; "titi"] in...
3 :: l
#open "list";;
tout en haut de votre code)
Il faut savoir qu'une liste c'est : un élément, et un pointeur sur une autre liste. En clair l'insertion d'un élément à la fin d'une liste... c'est plus compliqué que ça en a l'air
let affiche = function
| [] -> ()
| t :: q -> (print_int t; affiche q);;
Vous voyez, c'est du code rapide et joli !!!
Fonction qui applique une fonction a tous les éléments d'une listelet maper f = function
| [] -> []
| t :: q -> (f t) :: (maper f q);;
f est l'argument de notre fonction 'maper', (et le deuxième argument est une liste, mais ça se voit pas parce qu'on a écrit function pour éviter
d'avoir à écrire match liste with...
C'est pour ainsi dire le domaine de prédilection de CamL.
type montype = Ceci | Cela | UnEntier of int | Autre of montype;;
type laby = Mur | Arbre of string | Rien | Mechant of int
(string dira si c'est un peuplier, ... et Mechant le nombre de points
de vie/d'expérience de celui-ci)type maliste = Vide | Couple of int * maliste;;
Le type liste étant peut-être déjà implémenté en CamL, mais rien ne vous empêche d'essayer de faire le votre.
C'est pour les types utilisateurs que le match a été construit ! Ainsi, on écrira :
match liste with
| Vide -> ...
| Couple (b, c) -> ....;;
type arbre = Feuille of int | Noeud of arbre * arbre;;
type 'a liste = Vide | Triple of 'a * 'b * ('a liste);;
Vide | Couple of int * liste
puis pareil mais pour les listes de chaînes de caractère, puis ..., (alors que c'est la même structure au final), et bien là c'est ça.type personne = { prenom : string; mutable age : int };;
let thomas = {prenom = "thomas", age = "20"} in thomas.age <- 30;;
Vous avez pu le constater, CamL n'est pas fait pour utiliser Excel, gérer vos fichiers sur l'ordinateur, ou autre, mais vraiment faire de la programmation pure.
float_of_int, int_of_float, string_of_int, ...
print_int, print_newline(), print_string, print_float, ...
Printf.print
: "%d", %s et %f respectivement. Exemple : "%s a %d ans et %f part d'année" "Thomas" 21 0.34let lire_entier() = (fun i -> Scanf.scanf " %d" (fun x -> x))
; Voir Array.init pour lire un tableau. Remplacer %d par %s pour lire une chaîne
de caractères.a lsl i
failwith "erreur : division par 0"
Ça peut servir à 2 choses : quand on a envie de dire à une fonction qu'il faut s'arrêter là et ne pas continuer, parce qu'on est dans un cas où l'on n'est pas sensé être, mais que CamL pour compiler a besoin que notre fonction retourne la même chose que dans les autres cas. Plutôt que de mettre quelque chose de bidon, on met un petit failwith, et hop on est tranquille. Après, c'est aussi une manière de lancer une exception en CamL. On la rattrape avec try (ici le code où il y aura des exceptions à rattraper) with (et ici le compilateur va matcher ce que l'on recoit.
Exemple :
let f n =
if n = 1 failwith "trop petit";
if n = 2 failwith 2;
5;;
try
print_int (f 4)
with
| 2 -> ...
| "trop petit" -> ...
| _ -> (* une autre erreur, mais une erreur quand même, donc pas le cas où f renvoie 5 *)
A votre grand damne, il n'y en a pas vraiment beaucoup. Mais comme à tout malheur quelque chose est bon, CamL n'a pas non plus des dizaines d'API cachées, il n'y a que très peu de commandes différentes et à partir de là, les seuls problèmes que vous aurez seront de nature algorithmique. Je vous conseille de jeter un oeil à ma page de codes sources, car CamL est un langage de type fonctionnel et il faut s'y habituer. Cela ne veut pas dire que je suis un exemple à suivre, mais bon... regarder mon code peut vous donner une idée du type de programme à faire.
Je m'explique : si vous devez écrire la fonction factorielle(n), on peut faire :
let rec facto n =
if n = 1 then
1
else
n * (facto (n - 1));;
ou bien
let facto n =
let p = ref 1 in
for k = 1 to n
p := !p * k
done;
!p;;
La "bonne" manière de programmer est la première : le code est plus clair, et correspond plus à la manière dont la fonction est définie en vraie, bref plus intuitif. Pourtant, il y a n appels de fonctions, ce qui ralentit le programme. L'esprit CamL consiste à faire fi de la mémoire et du temps de programmation (pas complètement non plus, mais bon). En clair, vu la puissance des ordinateurs de nos jours, on n'est pas à 3 octets près. Donc autant écrire un code propre et net, et qui sera rapide à déboguer, qu'un espèce de gros machin destiné à faire économiser 1 CPU.
Elle cherche dans une liste de couples (clé,valeur) une clé et renvoie le cas échéant la valeur associée. On peut renvoyer -1, ou "" (ça dépend du type de valeur) si l'on ne trouve pas le résultat, ou un failwith, mais c'est énervant à écrire
let rec assoc x = function
| [] -> failwith "non"
| (a, b) :: q -> if a = x then b else assoc x q;;
Le type arbre n-aire est d'abord défini : soit c'est une feuille, soit c'est un noeud où il y a p branches (p pouvant varier entre les différents noeuds), et donc il se caractérise par une liste d'arbres n-aires. Puis l'on définit les grandeurs usuelles sur les arbres : nombres de feuilles, de noeuds, et profondeur d'un tel arbre.
type ('n, 'f) arbre_naire = Feuille of 'f | Noeud of 'n * ('n, 'f) arbre_naire list;;
let rec it_list compteur f = function
| [] -> compteur (* on renvoit le compteur une fois la liste parcourue en entier *)
| arbre :: resteliste -> it_list (compteur + f arbre) f resteliste;;
let rec nb_feuilles = function
| Feuille _ -> 1
| Noeud (_, l) -> it_list 0 nb_feuilles l);;
let rec nb_noeuds = function
| Feuille _ -> 0
| Noeud (_, l) -> it_list 1 nb_noeuds l);;
Pour comprendre un petit peu ce code, par exemple celui qui compte le nombre de feuilles : un arbre est soit une feuille donc il a 1 feuille, soit c'est une liste d'arbre, et c'est la somme du nombre de feuilles des arbres de cette liste. On a donc envie de faire n = 0 puis pour chaque arbre de cette liste, n = n + le nombre de feuilles de l'arbre. Mais c'est moche de l'écrire comme ça. On itère donc avec it_list sur chaque noeud de la liste. Le 'a' de l'argument de it_list joue donc le rôle de compteur, comme si on avait une variable globale que l'on incrémentait, sauf que là, elle se transmet, et f est la fonction de ce qu'on va compter. Ainsi pour le nombre de feuilles, on appelle it_list sur notre liste et 0 parce qu'on n'a encore rien compté. it_list va appliquer f, c'est à dire nb_feuilles à chaque arbre de la liste, et va donc ajouter tout ça.
idem avec la profondeur
let rec it_list2 a f = function
| [] -> a
| t :: q -> it_list2 (f a t) f q;;
let rec profondeur = function
| Feuille _ -> 1
| Noeud (_, l) -> (let f a b = max a (profondeur b) in
1 + it_list2 0 f l);;
Remarque : la fonction est ici plus complexe : a prend toujours le rôle de compteur, mais ici la fonction f prend 2 arguments. En effet,
on aurait pu écrire it_list3 de même, avec it_list3 (max a (f t)) f q
seulement, pour dire que la hauteur d'un arbre est le max des hauteurs de
chaque sous-arbre, mais en faisant ici une fonction it_list2 plus complexe, on couvre plus de cas : it_list dit qu'il faut ajouter l'argument 'a',
notre compteur, à tous les autres, et it_list3 dit qu'il faut en prendre le max. Et bien autant faire it_list2 qui nous laisse le choix de
fournir f.
Pour nb_feuilles, la fonction f passée en argument ne serait pas nb_feuilles, mais let f a b = (a + nb_feuilles b) in it_list2 0
f l;;
et idem pour nb_noeuds : let f a b = (a + nb_noeuds b) in it_list2 1 f l;;
Laissez un commentaire !
Pas besoin de vous connecter, commencez à taper votre nom et une case "invité" apparaîtra.