Objectif et plan

Objectif : découvrir la programmation fonctionnelle avec OCaml, comprendre pourquoi c'est important, et apprendre les bases.

  1. Histoire et importance d'OCaml
  2. Développer un programme OCaml (REPL, compilation)
  3. Fonctions : trois formes, variations, récursion
  4. Listes : construction, pattern matching, récurseur
  5. map, filter, fold…

Pourquoi OCaml est le plus important

Histoire : Né à l'INRIA (Caml → OCaml en 1996).

« 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 :

  1. Toplevel (REPL) : ocaml, tapez des expressions
  2. Script : ocaml prog.ml
  3. Compilation : ocamlc -o prog prog.ml ou ocamlopt -o prog prog.ml (natif)
# 1 + 2 * 3;;
- : int = 7
# let carre x = x * x;;
val carre : int -> int = <fun>
Exercice

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)
Exercice

É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 *)
Exercices

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.

Exercice

É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. »

Exercice

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)
Exercice

É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 :

Sucre syntaxique : [1; 2; 3] = 1 :: 2 :: 3 :: []

Pattern matching :

let rec somme lst = match lst with
  | [] -> 0
  | x :: xs -> x + somme xs
Exercice

É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] *)
Exercices

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.