\appendix \section{Annexe : autres monades avancées} \subsection{Monade CPS (Continuation Passing Style)} La monade CPS capture l'idée de « ce qu'on fait ensuite » — la \textbf{continuation}. Chaque fonction reçoit un argument supplémentaire qui représente la suite du calcul. Le type est : \begin{lstlisting} type ('a, 'r) cont = ('a -> 'r) -> 'r \end{lstlisting} Une valeur de type \texttt{('a,~'r)~cont} est une fonction qui, étant donné une continuation \texttt{('a~->~'r)}, produit un résultat \texttt{'r}. \subsubsection{Définition} \begin{lstlisting} let return x = fun k -> k x let (>>=) m f = fun k -> m (fun x -> f x k) \end{lstlisting} \begin{blague} CPS, c'est comme un restaurant où au lieu de commander et d'attendre votre plat, vous donnez au chef un numéro de téléphone pour qu'il vous appelle quand c'est prêt. Et le chef enchaîne les plats comme des continuations. Le problème, c'est quand le téléphone sonne dans le four. \end{blague} \subsubsection{Exemple 1 : sortie anticipée (early return)} On peut simuler un \texttt{return} style impératif : une continuation qui court-circuite le reste du calcul. \begin{lstlisting} (* Calcul avec sortie anticipee *) let cherche_dans_liste pred lst = let rec aux = function | [] -> None | x :: xs -> if pred x then Some x else aux xs in aux lst \end{lstlisting} Avec CPS, on peut faire mieux : \begin{lstlisting} (* Trouve le premier element pair et l'imprime *) let premier_pair_cps lst = let ret = ref None in let k_finale x = !ret in let k_trouve x = ret := Some x; k_finale 42 in List.iter (fun x -> if x mod 2 = 0 then k_trouve x ) lst; !ret \end{lstlisting} Bon, ce n'est pas encore très monadique. Voici un exemple plus parlant : la « coroutine » avec CPS. \subsubsection{Exemple 2 : generateur (coroutine)} Avec CPS, on peut suspendre et reprendre un calcul : \begin{lstlisting} type ('a, 'r) gen = ('a -> unit) -> ('a -> 'r) -> 'r let yield x = fun return suspend -> suspend x let rec range_gen n = if n <= 0 then fun return _ -> return () else yield n >>= fun _ -> range_gen (n - 1) \end{lstlisting} \subsubsection{Exemple 3 : amb (choix non-déterministe avec CPS)} L'opérateur \texttt{amb} (McCarthy) essaie toutes les valeurs possibles via CPS. C'est une version controlée du non-déterminisme : \begin{lstlisting} (* amb : choisit une valeur parmi une liste *) let amb lst = fun k_succ k_fail -> let rec essayer = function | [] -> k_fail () | x :: xs -> k_succ x (fun () -> essayer xs) in essayer lst (* Utilisation : trouver x,y,z dans [1..n] tels que x^2 + y^2 = z^2 *) let triple_pythagoricien n = let range = List.init n (fun i -> i + 1) in amb range >>= fun x -> amb range >>= fun y -> amb range >>= fun z -> if x * x + y * y = z * z then return (x, y, z) else fail \end{lstlisting} \begin{blague} L'opérateur \texttt{amb} est le CCSD (Chef de la Choisissabilité Supervisée) du monde des monades : il explore toutes les possibilités et revient en arrière si ça foire. Comme quand vous essayez tous les snacks du distributeur avant d'admettre que vous voulez juste un Mars. \end{blague} \newpage \subsection{Monade de Backpropagation (Différentiation Automatique)} La monade de backpropagation permet de calculer automatiquement les dérivées d'une fonction. On va présenter deux approches : la différentiation automatique en mode \textbf{forward} (nombres duaux) et en mode \textbf{reverse} (backpropagation). \subsubsection{Nombres duaux (mode forward)} Un nombre dual associe à une valeur $v$ sa dérivée $dv$ : \begin{lstlisting} type 'a dual = { v : 'a; d : 'a } \end{lstlisting} Les opérations arithmétiques propageant la dérivée via la règle de chaîne : \begin{lstlisting} let add a b = { v = a.v + b.v; d = a.d + b.d } let mul a b = { v = a.v * b.v; d = a.v * b.d + b.v * a.d } let sin a = { v = sin a.v; d = cos a.v *. a.d } \end{lstlisting} On peut en faire une monade si on veut chaîner des calculs avec dépendances : \begin{lstlisting} type 'a ad = { value : 'a; deriv : 'a; mutable tape : ('a ad * 'a ad) option } let return x = { value = x; deriv = 0.0; tape = None } let (>>=) m f = let r = f m.value in r.deriv <- m.deriv +. r.deriv; r \end{lstlisting} Cette présentation est simplifiée. L'implémentation réelle stocke un graphe de calcul (Wengert list / tape). \begin{blague} La différentiation automatique, c'est comme se rappeler tous les couloirs qu'on a empruntés dans un labyrinthe, mais à l'envers. Le mode forward, c'est votre GPS qui recalcule le chemin à chaque intersection. Le mode reverse, c'est le chauffeur Uber qui vous demande « par où on est passés déjà ? » à la fin du trajet. \end{blague} \subsubsection{Mode reverse (backpropagation) : une tape monadique} Le mode reverse est plus efficace pour les fonctions avec beaucoup de paramètres (réseaux de neurones). On enregistre toutes les opérations sur une \textbf{tape}, puis on la parcourt à l'envers pour propager les gradients. \begin{lstlisting} type tape = | Leaf of { mutable grad : float } | Add of node * node | Mul of node * node | Sin of node | Cos of node and node = { value : float; tape : tape } (* Construction du graphe *) let add a b = let r = { value = a.value +. b.value; tape = Add (a, b) } in r let mul a b = let r = { value = a.value *. b.value; tape = Mul (a, b) } in r (* Backpropagation : derivee partielle *) let rec grad_node n = match n.tape with | Leaf _ -> 1.0 | Add (a, b) -> grad_node a +. grad_node b | Mul (a, b) -> grad_node a *. b.value +. a.value *. grad_node b | Sin a -> cos a.value *. grad_node a | Cos a -> -.sin a.value *. grad_node a \end{lstlisting} On peut encapsuler \texttt{node} dans une monade pour chaîner naturellement : \begin{lstlisting} type 'a backprop = unit -> node let return x () = { value = x; tape = Leaf { grad = 0.0 } } let (>>=) m f () = let a = m () in (f a.value) () \end{lstlisting} Exemple d'utilisation : \begin{lstlisting} let f x = sin (mul x x) (* f(x) = sin(x^2) *) let x = { value = 2.0; tape = Leaf { grad = 0.0 } } let y = f x (* calcule sin(4) *) let dy = grad_node y (* derivee : cos(4) * 2*2 *) \end{lstlisting} \begin{blague} Avec la monade de backpropagation, vous pouvez dériver n'importe quelle fonction sans faire une seule ligne de calcul différentiel. C'est comme tricher à un examen de maths avec une calculatrice quantique. Vos profs de prépa vous détesteraient, mais vos réseaux de neurones vous adoreront. \end{blague} \newpage \subsection{Mini interprète Prolog} Le Prolog est un langage de programmation \textbf{logique} : on énonce des faits et des règles, et le moteur cherche les solutions par \textbf{unification} et \textbf{backtracking}. La monade Liste est parfaite pour modéliser le backtracking ! \subsubsection{Terms et substitution} \begin{lstlisting} type term = | Var of string | Const of string | Fun of string * term list type substitution = (string * term) list \end{lstlisting} \subsubsection{Unification} L'unification trouve la substitution la plus générale qui rend deux termes égaux : \begin{lstlisting} let rec unifie t1 t2 subst = match t1, t2 with | Var x, _ -> unifie_var x t2 subst | _, Var y -> unifie_var y t1 subst | Const a, Const b when a = b -> return subst | Fun (f, args1), Fun (g, args2) when f = g -> unifie_liste args1 args2 subst | _ -> fail \end{lstlisting} \subsubsection{Programme logique} Un programme est une liste de clauses (faits ou règles) : \begin{lstlisting} type clause = { tete : term; corps : term list } type programme = clause list \end{lstlisting} Le moteur d'inférence cherche à prouver un but en essayant toutes les clauses : \begin{lstlisting} let rec prouve programme buts subst = match buts with | [] -> return subst | but :: reste -> programme >>= fun clause -> (* Renommer les variables de la clause *) let clause' = renomme clause in unifie but clause'.tete subst >>= fun subst' -> prouve programme (clause'.corps @ reste) subst' \end{lstlisting} \subsubsection{Exemple : généalogie} \begin{lstlisting} let prog = [ (* parent(x,y) : x est parent de y *) fait "parent" ["jean"; "marie"]; fait "parent" ["marie"; "paul"]; fait "parent" ["paul"; "sophie"]; (* grandparent(x,y) :- parent(x,z), parent(z,y) *) regle "grandparent" ["x"; "y"] [atome "parent" ["x"; "z"]; atome "parent" ["z"; "y"]]; (* ancetre(x,y) :- parent(x,y) *) regle "ancetre" ["x"; "y"] [atome "parent" ["x"; "y"]]; (* ancetre(x,y) :- parent(x,z), ancetre(z,y) *) regle "ancetre" ["x"; "y"] [atome "parent" ["x"; "z"]; atome "ancetre" ["z"; "y"]]; ] (* Requete : ancetre(jean, X) ? *) (* Resultat : X = marie, X = paul, X = sophie *) \end{lstlisting} Quand on lance la requête \texttt{ancetre(jean, X)}, le moteur : \begin{enumerate} \item Essaie la première règle \texttt{grandparent} : échec (les noms ne correspondent pas). \item Essaie la règle \texttt{ancetre} avec \texttt{parent(jean,z)} : trouve \texttt{z=marie}. \item Puis \texttt{ancetre(marie, X)} : récursivement, trouve \texttt{X=paul}, puis \texttt{X=sophie}. \item La monade Liste collecte toutes les solutions. \end{enumerate} \begin{blague} Prolog, c'est comme un détective privé qui explore toutes les pistes en même temps. La monade Liste, c'est son tableau d'enquête avec des fiches de toutes les couleurs. Le backtracking, c'est quand il réalise que le majordome avait un alibi et qu'il défait tout le fil rouge pour suivre la piste du jardinier. \end{blague} \subsubsection{Code complet du mini Prolog} Voici le squelette complet (env. 60 lignes) : \begin{lstlisting} type term = Var of string | Const of string | Fun of string * term list type substitution = (string * term) list type clause = { tete : term; corps : term list } type programme = clause list (* Variables fraiches *) let compteur = ref 0 let fraiche _ = compteur := !compteur + 1; Var ("_" ^ string_of_int !compteur) let renomme clause = let rec aux = function | Var x -> fraiche () | Const _ as t -> t | Fun (f, args) -> Fun (f, List.map aux args) in { tete = aux clause.tete; corps = List.map aux clause.corps } (* Unification *) let rec unifie t1 t2 subst = match t1, t2 with | Var x, _ -> (try unifie (List.assoc x subst) t2 subst with Not_found -> return ((x, t2) :: subst)) | _, Var y -> unifie t2 t1 subst | Const a, Const b when a = b -> return subst | Fun (f, a1), Fun (g, a2) when f = g -> (try List.fold_left2 (fun s t1 t2 -> s >>= fun s' -> unifie t1 t2 s') (return subst) a1 a2 with Invalid_argument _ -> fail) | _ -> fail and fail = fun _ -> [] (* Moteur d'inference *) let rec prouve prog buts subst = match buts with | [] -> return subst | but :: reste -> prog >>= fun clause -> let clause' = renomme clause in unifie but clause'.tete subst >>= fun subst' -> prouve prog (clause'.corps @ reste) subst' (* Fonctions auxiliaires *) let const n = Const n let var n = Var n let atome nom args = Fun (nom, List.map (fun n -> Const n) args) let fait nom args = { tete = atome nom args; corps = [] } let regle nom args corps = { tete = atome nom args; corps } \end{lstlisting} \begin{blague} Vous venez d'écrire Prolog en 60 lignes d'OCaml. C'est environ 60 fois moins de lignes que le manuel d'utilisation de SWI-Prolog. Et ça compile du premier coup (enfin, après quelques tentatives). \end{blague} \subsection{Aller plus loin} Les monades présentées ici ne sont que la partie émergée de l'iceberg. Il en existe des dizaines d'autres : \begin{itemize} \item \textbf{State monad} : threader un état à travers des calculs (compteur, générateur d'ID, etc.). \item \textbf{IO monad} : gérer les entrées-sorties de manière pure (utilisée en Haskell). \item \textbf{Writer monad} : accumuler des logs ou des traces. \item \textbf{Reader monad} : partager un environnement de configuration. \item \textbf{Free monad} : construire des DSL (Domain Specific Languages) de manière modulaire. \item \textbf{Probability monad} : modéliser des distributions de probabilités (Monte Carlo, inférence bayésienne). \end{itemize} \begin{blague} On dit qu'un développeur fonctionnel qui maîtrise les monades peut expliquer le changement de couche à un bébé. On dit aussi qu'il peut être dangereusement productif et faire en une après-midi ce qu'une équipe de 10 développeurs Java fait en une semaine. Mais ça, c'est peut-être une légende urbaine. \end{blague} \vfill \hrule \vspace{2mm} \small Cette annexe complète le \texttt{cours\_monade.pdf}. Pour aller plus loin : le livre \textit{Real World OCaml}, la série d'articles \textit{You Could Have Invented Monads!} (et \textit{And Then You've Actually Invented Them}), et le site \texttt{https://learn-ocaml.org/}.