Objectif et plan
Objectif : découvrir la programmation fonctionnelle avec OCaml, comprendre pourquoi c'est important, et apprendre les bases.
- Histoire et importance d'OCaml
- Développer un programme OCaml (REPL, compilation)
- Fonctions : trois formes, variations, récursion
- Listes : construction, pattern matching, récurseur
- map, filter, fold…
Pourquoi OCaml est le plus important
Histoire : Né à l'INRIA (Caml → OCaml en 1996).
- Typage fort + inférence : le compilateur détecte les erreurs sans qu'on écrive les types
- Pattern matching exhaustif : un oubli → warning
- Immutabilité par défaut : une fonction = une fonction
- Performance : compilateur natif, proche du C
- Industrie : Jane Street (60M lignes), Facebook (Infer), Docker, Bloomberg
- Langage du prouveur Coq (preuve formelle)
« Une fonction pure, c'est comme un distributeur de bonbons : vous mettez une pièce, vous obtenez un bonbon. Si vous devez secouer la machine pour qu'elle marche, ce n'est plus une fonction pure. »
Comment développer un programme OCaml
Trois manières :
- Toplevel (REPL) :
ocaml, tapez des expressions - Script :
ocaml prog.ml - Compilation :
ocamlc -o prog prog.mlouocamlopt -o prog prog.ml(natif)
# 1 + 2 * 3;;
- : int = 7
# let carre x = x * x;;
val carre : int -> int = <fun>
Ouvrez ocaml. Calculez 210, définissez carre x = x * x, testez carre 12.
Les trois formes de fonctions
1. Anonyme (lambda) :
fun x -> x * x
fun x y -> x + y
2. Nommée :
let carre x = x * x
let somme a b = a + b
3. Récursive : let rec permet de s'appeler soi-même
let rec fact n =
if n <= 1 then 1
else n * fact (n - 1)
Écrivez max2 x y (max de deux entiers), puis max3 x y z utilisant max2 (sans max de la bibliothèque).
Fonctions : variations et application partielle
Plusieurs écritures, même résultat :
let add a b = a + b (* classique *)
let add = fun a b -> a + b (* lambda *)
let add = (+) (* alias d'opérateur *)
Application partielle :
let add5 = add 5 (* val add5 : int -> int *)
(* add5 3 → 8 *)
Opérateurs personnalisés :
let ( *** ) x y = x * x + y * y (* 3 *** 4 → 25 *)
1. Définissez incr = (+) 1 et testez incr 10.
2. Créez prefixe p n = p ^ " " ^ n puis dire_bonjour = prefixe "Bonjour".
La factorielle (récursion simple)
let rec factorielle n =
if n <= 1 then 1
else n * factorielle (n - 1)
Évaluation de factorielle 4 : 4 × (3 × (2 × (1 × 1))) = 24
Problème : chaque appel empile n × … sur la pile. O(n) en pile → débordement pour n grand.
Écrivez la suite d'appels de factorielle 6. Combien d'appels récursifs ? Quelle complexité ?
Récursion terminale (tail rec)
Idée : accumulateur pour le résultat partiel.
let rec fact_tail n acc =
if n <= 1 then acc
else fact_tail (n - 1) (n * acc)
let factorielle n = fact_tail n 1
Évaluation : fact_tail 4 1 → fact_tail 3 4 → fact_tail 2 12 → fact_tail 1 24 → 24
Plus de pile à déplier : OCaml transforme en boucle. O(n) en temps, O(1) en pile.
« La récursion terminale, c'est comme faire ses valises sans les défaire : on ajoute au fur et à mesure, et à la fin on n'a qu'à fermer la valise. »
Convertissez somme n = if n=0 then 0 else n + somme (n-1) en version tail-récursive. Testez somme 100.
Le récurseur sur ℕ
On extrait le motif de la récursion :
let rec recurse n z f =
if n = 0 then z
else f n (recurse (n - 1) z f)
recurse généralise toute récursion sur un entier :
let factorielle n = recurse n 1 ( * )
let somme n = recurse n 0 (+)
let puissance x n = recurse n 1 (fun _ acc -> x * acc)
Écrivez sum_pair n qui somme les entiers pairs de 0 à n avec recurse. Et si on veut les multiples de 3 ?
Listes en OCaml
Deux constructeurs :
[]— liste videx :: xs— ajoute x devant xs (cons)
Sucre syntaxique : [1; 2; 3] = 1 :: 2 :: 3 :: []
Pattern matching :
let rec somme lst = match lst with
| [] -> 0
| x :: xs -> x + somme xs
Écrivez longueur qui compte les éléments d'une liste. Testez longueur [5;2;8].
Récurseur sur listes (fold_right)
let rec recurse_list f z lst = match lst with
| [] -> z
| x :: xs -> f x (recurse_list f z xs)
C'est fold_right : on remplace [] par z, :: par f.
let produit = recurse_list ( * ) 1
let somme = recurse_list (+) 0
let longueur = recurse_list (fun _ acc -> acc + 1) 0
« Si vous avez compris recurse_list, vous avez compris la moitié d'OCaml. L'autre moitié, c'est type. »
map, filter, fold
let map f lst =
recurse_list (fun x acc -> f x :: acc) [] lst
(* map (fun x -> x*2) [1;2;3] → [2;4;6] *)
let filter p lst =
recurse_list (fun x acc ->
if p x then x :: acc else acc) [] lst
(* filter (fun x -> x mod 2 = 0) [1;2;3;4] → [2;4] *)
1. Écrivez list_find p lst qui retourne Some x pour le premier élément vérifiant p, None sinon.
2. Que donne fold_left (fun acc x -> x :: acc) [] [1;2;3] ? Et avec recurse_list (fold_right) ? Pourquoi la différence ?
« Les 3 piliers : Récursion, Polymorphisme, Ordre supérieur. Avec ces trois-là, on reconstruit la moitié de la bibliothèque standard en quelques lignes. Sans transpirer. »
Prochain cours
Cours 2 : le mot-clé type, les types algébriques (sommes, produits, arbres, AST), le pattern matching exhaustif, la compilation en bytecode.