« Monade est un mot qui fait peur. En réalité, une monade est juste un pattern qui revient tout le temps, comme le fait de devoir ranger sa chambre : c'est chiant, mais une fois qu'on a compris, tout est plus propre. »

Introduction

Rappel du cours 2 : Types algébriques, types sommes et produits, arbres binaires, AST, compilation en bytecode et machine à pile.

Ce cours : le pattern bind/return appliqué à option (calculs qui échouent), list (non-déterminisme, requêtes), et parsec (analyse syntaxique).

Le type option (rappel)

option = None (absence) ou Some x (valeur).

let safe_div a b =
  if b = 0 then None
  else Some (a / b)

Problème : chaîner devient verbeux.

let f x = match safe_div 10 x with
  | None -> None
  | Some y -> match safe_div y 2 with
    | None -> None
    | Some z -> Some (z + 1)

La monade option (bind/return)

let ( >>= ) opt f = match opt with
  | None -> None
  | Some x -> f x

let retour x = Some x

Avec >>= (bind), on chaîne les calculs élégamment :

let exemple =
  safe_div 10 2 >>= fun y ->
  safe_div y 2 >>= fun z ->
  retour (z + 1)
(* val exemple : int option = Some 3 *)

« La monade Option, c'est comme un colis Amazon : soit il arrive (Some), soit il est "perdu dans la chaîne logistique" (None). Et bind, c'est le livreur qui enchaîne les livraisons sans vous faire remplir un formulaire à chaque étape. »

Avant / Après

Sans monade :

let f x y z = match safe_div x y with
  | None -> None
  | Some a -> match safe_div a z with
    | None -> None
    | Some b -> Some (b + 1)

Avec monade :

let f x y z =
  safe_div x y >>= fun a ->
  safe_div a z >>= fun b ->
  retour (b + 1)

La monade Liste

'a list = plusieurs résultats possibles (non-déterminisme).

let retour x = [x]

let ( >>= ) m f = List.concat (List.map f m)
(* concat map : chaque élément peut produire 0, 1 ou N résultats *)

« Le bind de la monade Liste, c'est comme un distributeur de chewing-gums qui, pour chaque chewing-gum, vous en redonne plusieurs. Vous finissez avec une montagne de chewing-gums. »

Produit cartésien

let cartesian xs ys =
  xs >>= fun x ->
  ys >>= fun y ->
  retour (x, y)
(* cartesian [1;2;3] ["a";"b"] =
   [(1,"a"); (1,"b"); (2,"a"); (2,"b"); (3,"a"); (3,"b")] *)

Triplets pythagoriciens

let pythagore n =
  let r = List.init n (fun i -> i + 1) in
  r >>= fun a ->
  r >>= fun b ->
  r >>= fun c ->
  if a * a + b * b = c * c then retour (a, b, c)
  else []
(* pour n = 10 → [(3,4,5); (4,3,5); (6,8,10); (8,6,10)] *)

[] = échec (on filtre), retour = succès. On traduit l'énoncé mathématique littéralement.

Parties d'un ensemble (power set)

let rec parties = function
  | [] -> [[]]
  | x :: xs ->
    parties xs >>= fun p ->
    [p; x :: p]
(* parties [1;2;3] =
   [[]; [1]; [2]; [1;2]; [3]; [1;3]; [2;3]; [1;2;3]] *)

Monade Parsec — Analyse syntaxique

Problème : lire une chaîne, extraire une structure, gérer les erreurs.

type 'a parser = char list -> ('a * char list) option
(* [entrée] -> Some (résultat, reste) ou None *)

Le type dit tout : prend des caractères, retourne un résultat et le reste, ou échoue.

let retour x = (fun s -> Some (x, s))

let ( >>= ) p f =
  fun s -> match p s with
    | None -> None
    | Some (x, s') -> f x s'

Parseurs de base

let item = function          (* un caractère *)
  | [] -> None
  | c :: s -> Some (c, s)

let car c =                  (* un caractère précis *)
  item >>= fun x ->
  if x = c then retour x else None

let chiffre =                (* un chiffre *)
  item >>= fun c ->
  if '0' <= c && c <= '9' then retour c
  else None

On combine les parseurs avec >>= : car 'a' >>= fun _ -> car 'b' >>= fun _ -> chiffre lit "ab3" et retourne Some ('3', []).

Combinateurs

let ( <|> ) p q = fun s ->    (* p ou q *)
  match p s with
  | Some r -> Some r
  | None -> q s

let rec many p s =            (* 0 ou plus *)
  match p s with
  | None -> Some ([], s)
  | Some (x, s') ->
    many p s' >>= fun xs ->
    retour (x :: xs) s'

Des monades partout

Monade Type retour >>= (bind)
option'a optionSome xNone → skip
list'a list[x]concat map
parsecchar list → ...fun s → Some (x,s)passe l'état
writer'a * string list(x,[])concatène logs
asyncPromisePromise.resolve.then()

« Le pattern bind/return est partout. Une fois vu, vous le verrez dans Promise.then, Optional.flatMap (Java), $_.then (jQuery)… »

« Les monades, c'est comme les lasagnes : des couches. Mais contrairement aux lasagnes, on peut les composer sans avoir mal au ventre. »

Prochain cours

Cours 4 : on utilise tout ça pour implémenter un mini-réseau de neurones en OCaml pur.