Jeudi 7 avril 2005 – David Mentré
OCaml
caml.inria.fr
« Pourquoi c'est (presque ;-) le meilleur
langage du monde »
OCaml en quelques mots
●
OCaml est un langage de programmation :
– fortement typé, à typage polymorphe – à inférence de type
– types de données algébriques et filtrage – fonctionnel supérieur, impératif et objet – avec exceptions
– avec un ramasse miettes – efficace
– compilateur libre (QPL compatible DFSG & LGPL)
Petit historique
●
1985 : langages de la famille ML (dont Caml)
– développé à l'INRIA
– comme support pour des assistants de preuve
● par ex. : Coq de l'INRIA
●
1990 : système Caml Light
– byte code efficace
●
1996 : OCaml (Objective Caml)
– objet
– compilateur natif – ... et tout le reste
Démarrage : les types
●
Utiliser l'interpréteur pour découvrir OCaml
●
Toutes les expressions sont typées
$ ocaml
Objective Caml version 3.08.2 # 1;; - : int = 1 # "toto";; - : string = "toto" # [|1; 2|];; - : int array = [|1; 2|] # [true; false];;
Types de base, fonctions
●
Types classiques
– booléens, entiers, flottants, chaînes, tuples,
tableaux, listes, ...
– structures de données plus complexes
● ensembles, table de hachage, ...
●
Variables
●
Fonctions
# let carre x = x * x;;
val carre : int -> int = <fun>
# let pi = 4.0 *. atan 1.0;;
Avantages du typage d'OCaml
●
Avantage du typage fort ?
– détection des erreurs dès la compilation
●
Oui mais rentrer les types c'est lourd !
– c'est pourquoi on a une inférence de type
● le compilateur trouve les types, comme un grand
# let carre_flottant x = x *. x;;
val carre_flottant : float -> float = <fun> # let carre x = x * x;;
val carre : int -> int = <fun> # carre 4.0;;
Type Somme et filtrage
●
Définir les différentes alternatives
●
Traiter chaque cas dans une fonction
# type chocolat = Noir | Lait;;
type chocolat = Noir | Lait
# let j_aime = Noir;;
val j_aime : chocolat = Noir # let bon_chocolat choco = match choco with | Noir -> true
| Lait -> false;;
val bon_chocolat : chocolat -> bool = <fun> # bon_chocolat Noir;;
Avantage du filtrage
●
On n'oublie pas un cas !
– si on l'utilise de manière systématique, ces bugs
sont évités
● le compilateur le dit
– structures de données correctes par
construction
# let bon_chocolat choco = match choco with
| Noir -> true;;
Warning: this pattern-matching is not exhaustive. Here is an example of a value that is not matched:
Lait
Polymorphisme
●
Une fonction est générique
– elle marche sur plusieurs types ('a ≡ )
# List.rev;;
- : 'a list -> 'a list = <fun> # List.rev [1; 2; 3; 4];;
- : int list = [4; 3; 2; 1]
# List.rev ["langage"; "super"; "un"];;
- : string list = ["un"; "super"; "langage"] # List.rev [Noir; Lait];;
- : chocolat list = [Lait; Noir]
# List.map;;
Avantage du polymorphisme
●
Les programmes sont génériques
– fonctionnalités similaires dans d'autres langages
● ex. : Ada, Eiffel, template C++
– en OCaml, le polymorphisme arrive tout seul
avec l'inférence de type
●
Combinable avec d'autres constructions
– comme les types Somme
Filtrage et type somme polymorphe
# type 'a résultat = Trouvé of 'a | Pas_trouvé;;type 'a résultat = Trouvé of 'a | Pas_trouvé # let rec cherche elt liste =
match liste with
| tete :: reste when tete = elt -> Trouvé tete
| tete :: reste when tete <> elt -> cherche elt reste | _ -> Pas_trouvé;;
val cherche : 'a -> 'a list -> 'a résultat = <fun> # cherche Noir [Noir; Lait];;
- : chocolat résultat = Trouvé Noir
# cherche 0 [1; 2; 3; 4];;
Fonctions d'ordre supérieur
●
Des fonctions qui prennent en argument
d'autres fonctions
– utile pour être générique
# List.filter;;
- : ('a -> bool) -> 'a list -> 'a list = <fun> # List.filter (fun x -> x = 2) [1; 2; 3; 4];; - : int list = [2]
# List.filter (fun x -> x mod 2 = 0) [1; 2; 3; 4];; - : int list = [2; 4]
# List.filter (fun x -> x >= 2 && x < 4) [1; 2; 3; 4];; - : int list = [2; 3]
Style fonctionnel
●
Utiliser des fonctions récursives
– variables à assignation unique
● on leur donne une seul valeur : let x = ... in ....
– factorielle : n! = 1 * 2 * ... * n-1 * n
# let rec factorielle x =
if x <= 1 then 1 else x * factorielle (x - 1);; val factorielle : int -> int = <fun>
# factorielle 5;; - : int = 120
Style impératif
●
Comme la programmation « classique »
– variables peuvent être modifiées plusieurs fois
# let factorielle n =
let result = ref 1 in for i = 2 to n do
result := i * !result done;
!result;;
val factorielle : int -> int = <fun> # factorielle 5;;
Remarques sur le style fonctionnel
●
C'est efficace ?
– Oui car :
● une fonction récursive terminale est équivalente à
une boucle
– et le compilateur OCaml fait la transformation (comme gcc)
● en sous-main, on a un jeu de pointeur, pas de copie
réelle de données
●
Avantages : fonctionnel ou impératif ?
– cela dépend :-)
– le style fonctionnel permet souvent une
Programme récursif terminal
# let factorielle x = let rec f x acc =
if x <= 1 then acc else f (x - 1) (x * acc) in f x 1;;
val factorielle : int -> int = <fun> # let factorielle n =
let result = ref 1 in for i = 2 to n do
result := i * !result done;
!result;;
val factorielle : int -> int = <fun> # let rec factorielle x =
if x <= 1 then 1 else x * factorielle (x - 1);; val factorielle : int -> int = <fun>
●Fonctionnel
●Fonctionnel
terminal
Exemple de programme fonctionnel
# let rec tri liste =match liste with | [] -> []
| x :: l -> insère x (tri l) and insère elem liste = match liste with
| [] -> [elem] | x :: l ->
if elem < x then elem :: x :: l else x :: insère elem l;; val tri : 'a list -> 'a list = <fun>
val insère : 'a -> 'a list -> 'a list = <fun> # tri [2; 1; 0];;
Style objet
●
Comme en C++, Java, ...
– méthodes privées/publiques, virtuelles, ... – héritage multiple
– liaison tardive – ...
●
Avec en plus
– objets immédiats (objet sans classe) – méthodes polymorphes
Exemple de programme objet
# class point = object val mutable x = 0 method get_x = x method move d = x <- x + d end;; class point :object val mutable x : int method get_x : int method move : int -> unit end
# let p = new point;; val p : point = <obj> # p#get_x;; - : int = 0 # p#move 3;; - : unit = () # p#get_x;; - : int = 3
Exceptions
●
Comme en C++, Java, ...
– mais efficace
– donc utilisable comme structure de contrôle, pas
seulement sur les cas d'erreur
# exception Trouvé_à_l'index of int;; exception Trouvé_à_l'index of int
# let cherche x tableau =
for i = 0 to Array.length tableau - 1 do
if tableau.(i) = x then raise (Trouvé_à_l'index i) done;;
val cherche : 'a -> 'a array -> unit = <fun> # cherche 3 [|1; 4; 3; 7; 8|];;
Ramasse miettes
●
Récupération automatique de la mémoire
– ramasse miettes, Garbage Collector (GC)
●
On alloue des variables, fonctions, ...
– ... la libération est automatique – pas de segfault
● toute une classe de bugs en moins !
●
Ramasse miettes efficace
– contrairement à Java (du moins au début)
– techniquement : GC générationnel incrémental
● générationnel : vieux objets et petits nouveaux
Compilation
●
Deux compilateurs
– byte code sur une machine virtuelle (ocamlc)
● marche sur toutes les plate-formes ● efficace
● utilisé dans l'environnement interactif
– code natif (ocamlopt)
● pour x86, IA64, PowerPC, AMD64, Alpha, SPARC,
MIPS, PA-RISC, StrongARM
● très efficace
●
Plate-formes supportées
– Unix (Linux, *BSD, propriétaires), Windows,
Performances
langage score 1 C Intel 92,26 2 C gcc 85,99 3 OCaml 76,52 4 Ada 95 GNAT 63,49 ... ... ... 8 C++ Intel 44,67 ... ... ... 13 C# Mono 34,25 ... ... ... 18 Python 29,59 19 Perl 27,92 20 Tcl 26,47 21 Java 22,07 22 Ruby 18,15 ... ... ... 24 PHP 15,15shootout.alioth.debian.org
CPU : x1 Mem : x1 Lignes : x0●
OCaml est très efficace
– « au pire, 2 fois plus lent que le C »
– en pratique, quasiment aussi rapide que le C
● voire plus (gain sur
Calcul scientifique
Sieve primes up to 10^8 (bit-twiddling/array limited):
32-bit OCaml: 7.102s 32-bit C++: 19.145s
64-bit OCaml: 5.697s 64-bit C++: 13.433s
100th-nearest neighbours from a 10k-atom model of amorphous silicon (de/allocation limited):
32-bit OCaml: 28.407s 32-bit C++: 14.035s
64-bit OCaml: 35.538s 64-bit C++: 12.392s
Generate, bubble sort and accumulate an array of 10^4 double-precision random floating-point numbers:
32-bit OCaml: 1.185s 32-bit C++: 1.471s
64-bit OCaml: 0.785s 64-bit C++: 0.957s
without bounds checking:
32-bit OCaml: 0.992s 32-bit C++: 1.249s
64-bit OCaml: 0.591s 64-bit C++: 0.705s
2048^2 mandelbrot (float-arithmetic limited):
32-bit OCaml: 2.946s 32-bit C++: 1.479s
64-bit OCaml: 1.704s 64-bit C++: 1.161s
1024 FFTs and iFFTs (float-arithmetic limited):
32-bit OCaml: 31.491s 32-bit C++: 8.441s
64-bit OCaml: 9.260s 64-bit C++: 8.562s
Accumulate a Lorentzian over the number of integer triples (i, j, k) which lie in i^2 + j^2 + k^2 < 400 (float-arithmetic limited):
32-bit OCaml: 16.329s 32-bit C++: 8.002s
64-bit OCaml: 9.459s 64-bit C++: 5.933s
Jon Harrop / 2005-03-30 / caml-list
Utilisation d'OCaml
●
A éviter en OCaml
– programmation système et temps réel dur – boucles bien tassées
● C, C++, Ada
– petits scripts, traitement du texte, bidouille
● Perl, Python, shell script
– programmation objet « pure » (à mon avis)
● C++, SmallTalk, Eiffel, Python
●
Pour tout le reste, OCaml est utilisable
Environnement
●
Environnement de programmation complet
– debugger, compilateurs, lex et yacc, éditeurs
(mode Emacs), threads, ...
– pleins de bibliothèques, d'exemples, ...
● The Hump : http://caml.inria.fr/cgi-bin/hump.en.cgi
●
Communauté à croissance exponentielle
– liste de diffusion (très) active
● voire trollesque
Ce dont je ne parlerai pas
●
Modules fonctionnels (functors)
– encapsulation et abstraction des données
●
Liaison avec le langage C
●
Labels
●
Debugger avec retour arrière temporel
●
Évaluation paresseuse
# let f ~grand ~petit = grand - petit;;
val f : grand:int -> petit:int -> int = <fun> # f ~grand:3 ~petit:2;;
- : int = 1
# f ~petit:2 ~grand:3;; - : int = 1
Et les inconvénients !?
●
Langage de recherche
– plus difficile de trouver un programmeur – le langage évolue (mais en bien :-)
●
Pas autant de bibliothèques que Java ou C
●
Pas de debugger sur le code natif
●
Messages d'erreur parfois cryptiques
●
Quelques contraintes liées au typage
– pas de récursivité entre modules, ordre des
un exemple : demexp
●
Programme OCaml complet
– réseau, serveur, client graphique (GTK2), XML,
threads
– ~10.000 lignes de source
– ~6.000 lignes de code OCaml
● ½ serveur, ½ client
– versions Linux et Windows – 2 ans de développement
●
Bibliothèques externes
Conclusion
●
OCaml a de réelles qualités
– efficace, sûr, agréable, bien outillé, portable – on évite des bugs par construction
– c'est un vrai plaisir de programmer avec
● aussi rapide que le C, aussi sûr que l'Ada, aussi
souple que le Lisp (voire plus que les trois :-)
Pour démarrer
●
Site officiel
– http://caml.inria.fr/
– listes de discussions, sources, pointeurs
●