Découvrez Objective Caml
Lancelot PECQUET -
|
|
Objective Caml (Ocaml pour les intimes) est un langage de programmation magnifique: flexible, sûr, d'exécution rapide, doté de nombreuses bibliothèques (graphiques, réseau, ...). Il est souvent, à tort, considéré comme une curiosité qui n'intéresserait que quelques informaticiens théoriciens. Dans cet article, nous parcourrons rapidement l'arbre généalogique de ce langage pour en comprendre les principes fondateurs et familiariser le lecteur avec la programmation fonctionnelle; nous présenterons ensuite les différents modes de travail d'Ocaml et quelques aspects originaux de ce langage. Nous terminerons par un petit exemple d'utilisation des bibliothèques graphique et Unix et l'évocation des autres outils de développement disponibles pour Ocaml. |
Paru dans Linux Magazine n° 43 d'octobre 2002 |
Objective Caml est le résultat de plusieurs décennies d'évolution dans la conception des langages de programmation. Il partage avec son lointain ancêtre Lisp (créé au MIT dans les années 1960 et servant à programmer GNU Emacs) et son cousin éloigné scheme (utilisé dans The Gimp) la propriété d'être un langage fonctionnel, c'est-à-dire que les fonctions y ont un rôle privilégié comme nous le verrons plus bas.
Le langage C, créé au début des années 1970, n'a
pratiquement pas évolué depuis. C'est indéniablement un langage aux
multiples avantages, au premier rang desquels ses performances lors de
l'exécution, mais il « manque de structure » en un certain
sens. Comment être sûr, en effet, qu'un programme C ne terminera
pas de façon inopinée par un segmentation fault ou
réalisera bien la tâche qu'on en attend? On se contente souvent de
quelques tests pour répondre à ces questions, sans produire la moindre
garantie.
Même un langage récent comme Java souffre de carences
analogues puisqu'on voit Microsoft annoncer dans
ses licences (e.g. dans le paragraphe 9 du contrat de
licence de Windows 2000) que son
Sun l'oblige à mettre en
garde l'utilisateur éventuel de
Java sur ses risques potentiels. Voici donc ce que Microsoft
écrit sur la technologie de son rival de toujours:
|
L'interaction entre les mathématiques et l'informatique n'a pas cessé depuis l'émergence de cette dernière discipline et il n'est pas surprenant qu'on ait eu l'idée d'essayer de trouver un moyen de prouver mathématiquement qu'un programme fonctionnera et fera ce qu'on attend de lui, de manière à ce que la sûreté de fonctionnement ne repose plus uniquement sur des tests mais également sur des théorèmes. Si cette assurance est toujours la bienvenue, elle devient critique dans certaines circonstances comme lors du pilotage des centrales nucléaires et dispositifs médicaux sus-mentionnés... De telles garanties peuvent être exhibées pour des programmes Ocaml, en particulier lorsqu'il interagit avec des systèmes permettant à la preuve de programmes, comme le système Coq, développé par le Projet Logical de l'INRIA.
La notion de Méta Langage (ML), étudiée dès les années 1970 constitue un début de réponse à ce problème et la première implémentation de ce concept fut celle proposée à cette même époque par Robin MILNER: Standard ML.
Entre 1985 et 1990, une implantation efficace des concepts ML par les chercheurs du Projet CRISTAL de l'INRIA donna naissance au langage Caml (le mot « CAML » est un acronyme de « Categorical Abstract Machine Language »). Au début des années 1990, une implantation à machine virtuelle baptisée Caml-Light lui a succédé, elle-même désormais remplacée par Objective Caml qui permet, outre les traits impératifs, la programmation orientée objet. On dispose ainsi du style de programmation le mieux adapté à chaque situation.
Ocaml est en constant développement et bénéficie des dernières avancées de l'informatique théorique comme, par exemple, son remarquable GC (garbage collector ou glaneur de cellules: le gestionnaire automatique de mémoire).
Il se trouve que les chercheurs en informatique ne se sont pas exactement tourné les pouces ces trente dernières années alors pourquoi ne pas bénéficier de leurs efforts?
|
Je ne m'attarderai pas sur l'installation qui ne devrait pas poser de
problème puisqu'elle est on ne peut plus standard. Sont disponible sur
http://caml.inria.fr
des binaires précompilés pour Linux, MS Windows 2000, NT, ME, 98,
95, Mac OS 9 et OS X et des distributions complètes avec
sources sous forme de .rpm ou .tar.gz. Notez
qu'Ocaml est souvent présent dans les distributions classiques de
Linux (par exemple dans ma Mandrake 8.2), ce
qui rend son installation d'autant plus facile.
Les programmes écrits en Ocaml peuvent être compilés, soit vers du
code-octet portable avec la commande ocamlc, exécutés par
une machine virtuelle baptisée ocamlrun, soit vers du
code natif avec la commande ocamlopt. Il est possible
d'écrire directement du code Ocaml dans un terminal (ou mieux: dans
Emacs avec le mode tuareg) dans lequel on aura
préalablement lancé la boucle d'interaction Ocaml avec la
commande ocaml qui compilera ce code à la volée.
Pour commencer, plaçons-nous dans un terminal et invoquons la boucle d'interaction, l'invite de notre shell devient un dièse:
$ ocaml
Objective Caml version 3.04
# let x = 1 + 1 ;;
val x : int = 2
# #quit ;;
$
|
Comme on peut s'en douter, let x = 1 + 1 signifie
« soit x le résultat du calcul 1 +
1 ». Cette instruction se termine par un double
point-virgule (nous justifierons cette curiosité plus loin). La
réponse d'Ocaml à cette instruction est la ligne val x : int =
2 qui se lit « la valeur x est de type
int et vaut 2. ». Nous découvrons au passage
deux caractéristiques d'Ocaml:
le langage est fortement typé: toute valeur manipulée par Ocaml a un type bien défini (ici entier); la vérification de la compatibilité entre les types permet d'éliminer la plupart des erreurs introduites par maladresse ou étourderie et contribue à la sûreté de l'exécution;
le typage est inféré automatiquement: l'utilisateur n'a pas à faire de déclarations de type, tous les types sont déduits automatiquement de la syntaxe des expressions.
Enfin, on quitte Ocaml avec la directive #quit. Celle-ci
est préfixée par un dièse pour mettre en évidence qu'il s'agit d'une
commande ne faisant pas partie du langage de description des
programmes Ocaml mais destinée à contrôler le système Ocaml lui-même.
La boucle d'interaction est souvent utilisée pour développer rapidement une application en faisant quelques tests sans avoir à passer par le cycle écriture → compilation → exécution.
Un source Ocaml écrit dans un éditeur de texte pourra être, au choix,
importé dans la boucle d'interaction avec la directive
#use (voire copié-collé s'il ne s'agit que de quelques
lignes), compilé vers du code-octet (bytecode) portable, ou
vers du code natif.
Un point important est que le typage est statique, c'est-à-dire décidé à la compilation. Dès lors, il n'est pas nécessaire de faire ces vérifications durant l'exécution du programme ce qui accroît son efficacité.
La boucle d'interaction peut être personnalisée pour accéder aux
bibliothèques. On crée alors, grâce à la commande
ocamlmktop, une nouvelle boucle d'interaction avec les
options qu'on aura choisies.
Illustrons cette compilation avec notre programme préféré:
$ cat > hw.ml print_string "Hello World\n" ;; ^D $ ocamlc hw.ml $ file a.out a.out: a /usr/bin/ocamlrun script text executable $ head -n 1 a.out #!/usr/bin/ocamlrun $ ./a.out Hello World |
L'instruction print_string écrit son argument (noter
l'absence de parenthèses autour de celui-ci, nous y reviendrons) sur
la sortie standard. Le programme a.out est généré par le
compilateur ocamlc et sa première ligne invoque
automatiquement la machine virtuelle ocamlrun.
On compile le même fichier que ci-dessus avec le compilateur natif:
$ ocamlopt hw.ml $ file a.out a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), not stripped $ ./a.out Hello World |
Voilà le minimum vital. Passons maintenant au langage Ocaml proprement dit.
Nous l'avons annoncé plus haut: Ocaml est un langage fonctionnel. Cela signifie en particulier que les fonctions sont des valeurs de première classe: elles peuvent être arguments d'autres fonctions, ou résultat d'un calcul.
Les programmeurs des langages impératifs (et objets) désignent généralement par fonction un gadget pratique pour regrouper et réutiliser des instructions qu'il serait fatigant ou inefficace de réécrire. Des variantes terminologiques existent: procédure lorsque les fonctions n'ont pas d'argument, méthodes lorsque ces fonctions sont attachées à des objets.
Il faut cependant prendre garde aux effets de bord, c'est-à-dire au fait que ces « fonctions » sont susceptibles de modifier un certain nombre de variables, explicitement ou pas. Prenons par exemple le programme C suivant:
$ cat > f.c
#include<stdio.h>
int main(void)
{
int x = 0, y = 0;
int f(int z){x++; return x+z;}
printf("y=%d, f(y)=%d\n",y,f(y));
printf("y=%d, f(y)=%d\n",y,f(y));
return 0;
}
$ gcc -Wall f.c
$ a.out
y=0, f(y)=1
y=0, f(y)=2
|
Ni le code source de f, ni la valeur
de y n'ont changé explicitement entre les deux
printf. Mathématiquement, f n'est pas
une fonction Z → Z (la lettre Z désigne
l'ensemble des entiers), car sinon, on a la contradiction
f(0) = 1 = 2.
En C, la syntaxe x = 0 suggère que
l'« entier x est égal à 0 ». En
réalité, il s'agit d'une affectation, c'est-à-dire la
constitution d'un lien entre le nom x et un espace
mémoire contenant la valeur 0. L'exécution de la
fonction f modifier cette valeur. Après le premier
appel de f, l'« entier x est
égal à 1 »: cette « égalité » dépend du temps.
En Ocaml, le fait d'écrire let x = 0 ;; n'est pas
une affectation au sens ci-dessus car aucune fonction ne pourra
changer ce qui est une vraie égalité mathématique, indépendante du
temps. En d'autres termes, cela signifie que x est un
synonyme de 0. Seule une redéfinition volontaire et
explicite (comme en mathématiques) comme, par exemple, let x = 1
;; pourra changer la valeur de x.
La présence du double point-virgule souligne l'invariance dans le
temps des égalités lorsqu'on adopte un style fonctionnel de
programmation dans Ocaml tandis que le point-virgule simple séquence
des instructions devant être exécutées dans un ordre précis lors de
l'utilisation des traits impératifs d'Ocaml (boucles
for et while, pointeurs, tableaux,
etc), comme on le verra dans l'exemple complet à la fin de cet
article.
En pratique, les effets de bord peuvent être pratiques (pour faire un tri sur place), voire indispensable (pour les entrées-sorties) mais il faut garder à l'esprit que la décision d'implémenter une fonction avec effet de bord affaiblit la structure mathématique du programme et donc la possibilité d'en prévoir le comportement.
Donnons maintenant un exemple de fonction Ocaml, la fonction successeur x ↦ x + 1:
# let succ = function x -> x + 1 ;; val succ : int -> int = <fun> # x ;; Unbound value x |
La ligne se lit « soit succ, la fonction qui, à tout
x associe x + 1 ». Ocaml déduit de
l'expression x + 1 que x est de type
int et il infère automatiquement le type de la valeur
succ: c'est une fonction de type int -> int,
c'est-à-dire prenant un entier en entrée et renvoyant un entier en
sortie. On note qu'une fois l'instruction exécutée, x
n'est plus connu: il n'a un sens que dans portée de la définition de
la fonction où il lie la valeur de sortie (x + 1)
à celle de l'entrée x (bound est le participe
passé du verbe
to bind: lier). Une variante syntaxique est:
# let succ x = x + 1 ;; val succ : int -> int = <fun> |
Appliquons la fonction succ à un entier:
# succ 1 ;; - : int = 2 |
Le tiret signifie que la valeur précédente n'est pas nommée. Notons d'ailleurs que les fonctions Ocaml n'ont pas besoin non plus d'être nommées (auquel cas, elles sont dites anonymes) pour être utilisées. On aurait, en effet, pu écrire:
# (function x -> x + 1) 1 ;; - : int = 2 |
Le type unit est à peu près l'équivalent du type
void en C. Par exemple, on a:
# print_string ;; - : string -> unit = <fun> # Random.bits ;; - : unit -> int = <fun> # Random.bits () ;; - : int = 655733195 |
En effet, la fonction print_string affiche une chaîne de
caractères et renvoie la valeur () qui est de type
unit. La « procédure »
Random.bits est une fonction qui a pour argument
() et qui renvoie une suite de bits aléatoires sous la
forme d'un entier (les entiers Ocaml sont codés sur 31 bits, afin
d'exploiter le bit restant pour différencier valeurs immédiates de
pointeurs lors de la récupération automatique de mémoire). La forme
particulière du nom de cette fonction signifie qu'elle appartient au
module Random.
Lorsqu'on parle de fonctions « à plusieurs variables », on parle en fait de fonctions dont l'argument est un couple, un triplet, etc. En d'autres termes, l'ensemble de départ de la fonction est un produit cartésien. Par exemple:
# let add = function (x,y) -> x + y ;; val add : int * int -> int = <fun> # add (2,3) ;; - : int = 5 |
La fonction add a pour argument un couple
(x,y) qui est de type int * int
(mathématiquement, cela veut dire que ce couple appartient au produit
cartésien Z x Z).
Pour modéliser la notion de fonction à plusieurs variables dans les
langages fonctionnels, on préfère définir des fonctions dont le
résultat est une fonction (on dit qu'elle est curryfiée, du nom
de Haskell
CURRY (1900-1982) qui a popularisé cette méthodologie). Cela est
justifié par la théorie mathématique principale dont sont issus ces
langages: le
lambda-calcul (le « lambda » provenant du fait que
la lettre grecque λ apparaît tout le temps dans les
expressions mathématiques qu'on manipule dans cette théorie).
Ainsi, on préférera définir la fonction add_curry qui à
tout x associe la fonction qui à tout y
associe l'entier x + y:
# let add_curry = function x -> (function y -> x + y) ;; val add_curry : int -> int -> int = <fun> # add_curry 2 3 ;; - : int = 5 |
Pour plus de lisibilité, on n'a pas de parenthèses, ni lors de
l'affichage du type int -> int -> int qu'il faut lire:
int -> (int -> int), ni dans l'appel de la fonction sur
ses arguments qu'il faut lire ((add_curry 2) 3): en
effet, la fonction (add_curry 2) est la fonction qui à
tout entier y associe l'entier 2 + y;
l'écriture ((add_curry 2) 3) est donc 2 + 3 =
5. Un avantage de la curryfication est qu'on obtient une
fonction parfaitement valide en ne spécialisant qu'un seul des
arguments; on peut, par exemple, retrouver la fonction successeur en
écrivant:
# let succ' = add_curry 1 ;; val succ' : int -> int = <fun> # succ' 1 ;; - : int = 2 |
On notera au passage que les identificateurs peuvent comporter le caractère apostrophe (sauf en première lettre où il a une signification différente) pour représenter un « prime ».
On préférera l'une des deux variantes syntaxiques suivantes à celle donnée plus haut:
# let add_curry = fun x y -> x + y ;; val add_curry : int -> int -> int = <fun> # let add_curry x y = x + y ;; val add_curry : int -> int -> int = <fun> |
Un autre héritage du lambda-calcul est la façon de définir les variables locales:
# let x = 1 in x + 1 ;; - : int = 2 |
L'expression se lit « soit x = 1 dans l'expression
x + 1 », ou encore « soit l'expression x
+ 1 dans laquelle x vaut 1 ». Voici donc une
fonction qui calcule le carré du maximum entre deux entiers:
# let f x y =
let m = max x y in m * m ;;
val f : int -> int -> int = <fun>
# f 3 10 ;;
- : int = 100
|
La récursivité est l'outil de base en programmation fonctionnelle pour
remplacer les boucles. On peut en effet toujours théoriquement
implémenter avec des fonctions récursives les notions de boucle
for et de boucle while. Prenons l'exemple de
la fonction factorielle. En style impératif, on va utiliser une
référence sur un entier qui sera modifié à chaque passage dans une
boucle for:
# let fact n =
let result = ref 1 in
for i = 1 to n do
result := i * !result;
done;
!result;;
val fact : int -> int = <fun>
|
Le point d'exclamation précédent la référence désigne la valeur qui
lui est associée à cet instant et le := est bien un
symbole d'affectation. Noter les points-virgule simples. En utilisant
une fonction récursive, on évite les les effets de bord. Par ailleurs,
beaucoup d'algorithmes, comme celui du calcul la fonction factorielle,
sont naturellement récursifs et gagnent en clarté et en efficacité à
être implémentés dans un langage dans lequel cette récursivité pourra
s'exprimer. Ainsi, le code devient, en style fonctionnel:
# let rec fact n = if (n = 0) then 1 else n * fact (n-1) ;; val fact : int -> int = <fun> # fact 5 ;; - : int = 120 |
Le filtrage de motifs améliore considérablement l'expressivité
d'Ocaml: c'est du switch de C:, en beaucoup plus
puissant.
# let rec fact = function
0 -> 1
| n -> n * fact (n - 1);;
val fact : int -> int = <fun>
|
On peut définir des types somme, exploitables lors des filtrages. Par exemple (attention à la casse):
# type jour = Lundi | Mardi | Mercredi | Jeudi | Vendredi | Samedi | Dimanche ;;
type jour = Lundi | Mardi | Mercredi | Jeudi | Vendredi | Samedi | Dimanche
# let grasse_matinee = function
Dimanche -> true
| _ -> false ;;
val grasse_matinee : jour -> bool = <fun>
# grasse_matinee Dimanche ;;
- : bool = true
# grasse_matinee Lundi ;;
- : bool = false
|
Le trait bas (underscore) représente un élément quelconque mais
comme le filtrage se fait séquentiellement, la fonction ne renvoie
true que si son argument est Dimanche. Une
variante syntaxique de la fonction grasse_matinee est:
# let grasse_matinee j = match j with
Dimanche -> true
| _ -> false ;;
val grasse_matinee : jour -> bool = <fun>
|
Dans Ocaml, le typage est polymorphe paramétrique: l'algorithme
de typage reconnaît le type générique d'une fonction dont les
paramètres - dits polymorphes - ne sont pas totalement spécifiés; cela
permet de développer un code générique réutilisable dans tous les
contextes compatibles avec ce type polymorphe. Prenons le cas
de la fonction List.map, qui applique une fonction sur
une liste:
# List.map grasse_matinee [Lundi;Mardi;Mercredi;Jeudi;Vendredi;Samedi;Dimanche] ;;
- : bool list = [false; false; false; false; false; false; true]
# List.map succ [1;2;3;4;5] ;;
- : int list = [2; 3; 4; 5; 6]
# List.map ;;
- : ('a -> 'b) -> 'a list -> 'b list = <fun>
|
Les types préfixés par une apostrophe désignent des types quelconques
(des variables de type), mais on constate qu'il y a une
distinction entre 'a et 'b. En effet, si le
premier argument de List.map prend des arguments d'un
certain type 'a et renvoie des valeurs d'un certain type
'b, le deuxième argument de List.map doit
être une liste d'éléments de type 'a et le résultat final
sera une liste d'éléments de type 'b. La généricité est
maximale tout en continuer de garantir la compatibilité des arguments.
xclock -digital » personnalisé en Ocaml
Nous passons maintenant à un exemple concret: un petit programme qui
ouvre une fenêtre X et y affiche la date et l'heure, à la manière
de ce que ferait la commande xclock -digital mais sur
deux lignes, et en français.
let width = 150 ;; (* largeur de la fenetre *)
let height = 50 ;; (* hauteur de la fenetre *)
(* On ouvre la fenetre: *)
Graphics.open_graph (" " ^ string_of_int(width) ^ "x" ^ string_of_int(height)) ;;
(* La police par defaut devient 9x15bold: *)
Graphics.set_font("9x15bold") ;;
(* On definit un tableau des jours et des mois: *)
let days = [|"Dim";"Lun";"Mar";"Mer";"Jeu";"Ven";"Sam" |] ;;
let months = [| "Jan";"Fev";"Mar";"Avr";"Mai";"Jun";
"Jul";"Aou";"Sep";"Oct";"Nov";"Dec"|] ;;
(* Boucle principale: *)
while true do
Unix.sleep 1;
Graphics.set_color Graphics.blue ; (* couleur par defaut devient bleu *)
Graphics.fill_rect 0 0 width height ; (* dessine le fond *)
Graphics.set_color Graphics.yellow ;(* couleur par defaut devient jaune *)
let time = Unix.localtime (Unix.gettimeofday ()) in (* On recupere:*)
let h0 = string_of_int(time.Unix.tm_hour) and (* heure *)
m0 = string_of_int(time.Unix.tm_min) and (* minutes *)
s0 = string_of_int(time.Unix.tm_sec) and (* secondes *)
d0 = string_of_int(time.Unix.tm_mday) in (* jour du mois *)
(* on va eventuellement prefixer par zero, c'est plus joli: *)
let time_string =
(if String.length h0 = 2 then h0 else "0"^h0) ^":"^
(if String.length m0 = 2 then m0 else "0"^m0) ^":"^
(if String.length s0 = 2 then s0 else "0"^s0) and
day_string =
days.(time.Unix.tm_wday) ^" "^
(if String.length d0 = 2 then d0 else "0"^d0) ^" "^
months.(time.Unix.tm_mon) ^" "^
string_of_int(1900+time.Unix.tm_year) in
(* On centre et on dessine le tout: *)
let (day_width,text_height) = Graphics.text_size day_string and
(time_width,_) = Graphics.text_size time_string in
Graphics.moveto (width/2 - day_width/2) (height/2) ;
Graphics.draw_string day_string ;
Graphics.moveto (width/2 - time_width/2)
(height/2 - text_height) ;
Graphics.draw_string time_string ;
done;;
|
On compile ce programme avec le Makefile:
bytecode: xfrdate.ml ocamlc -custom graphics.cma unix.cma -o xfrdate xfrdate.ml -cclib -lunix -cclib -lX11 native: xfrdate.ml ocamlopt graphics.cmxa unix.cmxa -o xfrdate xfrdate.ml -cclib -lunix -cclib -lX11 clean: rm -f *.cmi *.cmo xfrdate |
|
Outre les programmes que nous avons déjà cités (ocaml,
ocamlc, ocamlopt,
ocamlrun), il existe un environnement complet de
développement comportant, entre autres:
un debugger (ocamldebug) permettant la
pose de points d'arrêt, l'exploration de variables, pour trouver et
réparer facilement les erreur et même remonter en arrière dans
l'exécution (!) ;
un profiler
(ocamlprof) permettant
d'optimiser les programmes écrits en Ocaml;
un mode Emacs
(tuareg) permettant
de développer plus rapidement (colorisation automatique de la syntaxe,
y compris lors de l'utilisation de la boucle d'interaction dans Emacs,
indentation automatique, raccourcis-clavier, etc.
Ocaml certainement l'un des meilleurs langages de programmation à l'heure actuelle. Il bénéficie à la fois d'une grande structure qui accroissent sa sûreté d'exécution, d'une syntaxe très expressive et d'une implémentation redoutablement efficace - comparable à celle de C dans beaucoup de circonstances - et à la pointe des dernières avancées de la recherche en informatique.
Au cours de ce long article, nous avons vus quelques principes de base du développement en Ocaml mais nous n'avons malheureusement même pas eu le temps de décrire son mécanisme d'exceptions, ou encore de programmation orientée objet incluant un système de modules permettant la dissociation de l'interface avec l'implantation ainsi qu'un système de classes avec héritage multiple.
Le langage Ocaml est activement développé à travers le monde et bénéficie de nombreuses bibliothèques: entiers en précision arbitraire, analyses lexicale et syntaxique, processus légers (multithreading), interface graphique, réseau,... On peut également, depuis Ocaml, appeler une fonction C et vice versa.
Ocaml est largement utilisé dans l'éducation (en classes préparatoires aux grandes écoles d'ingénieur et à l'université notamment) et la recherche mais également dans l'industrie. Nous espérons que la lecture de cet article vous a donné envie de rejoindre la communauté des hackers esthètes, programmeurs Ocaml.
Pour poursuivre cette découverte, le lecteur pourra se reporter aux
ouvrages cités en référence et surtout se reporter à http://caml.inria.fr sur
lequel il trouvera une multitude de liens, dont celui de la
documentation en ligne d'Ocaml:
http://caml.inria.fr/ocaml/htmlman/. Mentionnons
enfin l'existence du newsgroup fr.comp.lang.caml pour
les discussions exotiques.
CHAILLOUX, Emmanuel, MANOURY, Pascal et PAGANO, Bruno. Développement d'applications avec Objective Caml.
O'Reilly. 2000. Une version complète du livre (en langue française et anglaise) est disponible librement en ligne sur
http://www.pps.jussieu.fr/Livres/ora/DA-OCAML/index.html.
COUSINEAU, Guy et MAUNY, Michel. Approche fonctionnelle de la programmation. Ediscience international. 1995.
PECQUET, Lancelot. Programmation Fonctionnelle. Introduction illustrée en Objective Caml. Disponible librement en ligne
sur http://www-rocq.inria.fr/~pecquet/teach.html.
WEIS, Pierre et LEROY, Xavier. Le langage Caml. Dunod. 2ème édition. 1999.