diff --git a/README.md b/README.md index 448c3e2221cd23727ad1b8dd0808bac8f9afc14c..aafcf56054e00eab7127230ebfd1568b3ab005fd 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,13 @@ | 30/01 | 8h | TD + QCM | [Sujet](td/lifpf-td2-enonce.pdf), [corrigé](td/lifpf-td2-correction.pdf) | | | 9h45 ou 11h30 | TP | [Sujet](tp/tp2.md), [corrigé](tp/tp2.ml) <br> Groupe de TP, horaire et salle sur [tomuss] | | 20/02 | 8h | CM | [Diapositives](cm/lifpf-cm3.pdf) | -| | 9h45 ou 11h30 | TP | [Sujet](tp/tp3.md) <br> Groupe de TP, horaire et salle sur [tomuss] | +| | 9h45 ou 11h30 | TP | [Sujet](tp/tp3.md), [corrigé](tp/tp3.ml) | | 27/02 | 8h | TD + QCM | [Sujet](td/lifpf-td3-enonce.pdf), [corrigé](td/lifpf-td3-correction.pdf) | -| | 9h45 ou 11h30 | TP | [Sujet](tp/tp4.md) <br> Groupe de TP, horaire et salle sur [tomuss] | +| | 9h45 ou 11h30 | TP | [Sujet](tp/tp4.md), [corrigé](tp/tp4.ml) | | 06/03 | 8h | CM | [Diapositives](cm/lifpf-cm4.pdf) | -| | 9h45 | TD + QCM | [Sujet](td/lifpf-td4-enonce.pdf) | +| | 9h45 | TD + QCM | [Sujet](td/lifpf-td4-enonce.pdf), [corrigé](td/lifpf-td4-correction.pdf) | +| 13/03 | 8h | CM | [Diapositives](cm/lifpf-cm5.pdf) | +| | 9h45 ou 11h30 | TP | [Sujet](tp/tp5.md) <br> Groupe de TP, horaire et salle sur [tomuss] | ###### Évaluation diff --git a/cm/lifpf-cm5.pdf b/cm/lifpf-cm5.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d1e40f0a69ac56140f7f2c8e546be24562f14d39 Binary files /dev/null and b/cm/lifpf-cm5.pdf differ diff --git a/td/lifpf-td4-correction.pdf b/td/lifpf-td4-correction.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4484105cb5ce456443f6e70c959e68c0ba7e151d Binary files /dev/null and b/td/lifpf-td4-correction.pdf differ diff --git a/tp/tp3.ml b/tp/tp3.ml new file mode 100644 index 0000000000000000000000000000000000000000..7599c79c9c140802121dd6c1eb4e794553a41325 --- /dev/null +++ b/tp/tp3.ml @@ -0,0 +1,214 @@ +(* LIFPF TP3 Récursion sur les arbres *) + +(**********************************************************************) +(* Arbres binaires *) +(**********************************************************************) + +(** +Arbres binaires avec feuilles vides, +le contenu est seulement sur les noeuds. +*) +type arbre_bin = ABVide | ABNoeud of int * arbre_bin * arbre_bin + +(* Quelques arbres pour tester *) +let ab1 = ABNoeud (3, ABVide, ABVide) +let ab2 = ABNoeud (5, ab1, ABVide) +let ab3 = ABNoeud (7, ABVide, ab1) +let ab4 = ABNoeud (11, ab2, ab3) + +(** +Taille d'un arbre binaire. +@param a l'arbre dont on veut calculer la taille +@return le nombre d'int stockés dans l'arbre +*) +let rec taille_ab (a : arbre_bin) : int = + match a with + | ABVide -> 0 + | ABNoeud (_, fg, fd) -> 1 + taille_ab fg + taille_ab fd +;; + +assert (taille_ab ab1 = 1);; +assert (taille_ab ab2 = 2);; +assert (taille_ab ab3 = 2);; +assert (taille_ab ab4 = 5) + +(** +Fait le produit des éléments d'un arbre binaire. +Un arbre vide aura 1 comme produit +@param a l'arbre dont on veut faire le produit des éléments +@return le produit (1 pour l'arbre vide) +*) +let rec produit_ab (a : arbre_bin) : int = + match a with + | ABVide -> 1 + | ABNoeud (n, fg, fd) -> n * produit_ab fg * produit_ab fd +;; + +assert (produit_ab ABVide = 1);; +assert (produit_ab ab1 = 3);; +assert (produit_ab ab2 = 15);; +assert (produit_ab ab3 = 21);; +assert (produit_ab ab4 = 3465) + +(** +Construit la liste des éléments d'un arbre binaire. Les éléments sont produits +dans l'ordre de parcours infix, c'est à dire les éléments du fils gauche puis +l'élément du noeud puis ceux fils droit. + +@param a l'arbre binaire dont on veut les éléments +@return la liste des éléments de l'arbre +*) +let rec list_of_arbre_bin (a : arbre_bin) : int list = + match a with + | ABVide -> [] + | ABNoeud (n, fg, fd) -> + (* On peut aussi utiliser la fonction concatene du TP2 *) + list_of_arbre_bin fg @ (n :: list_of_arbre_bin fd) +;; + +assert (list_of_arbre_bin ABVide = []);; +assert (list_of_arbre_bin ab1 = [ 3 ]);; +assert (list_of_arbre_bin ab2 = [ 3; 5 ]);; +assert (list_of_arbre_bin ab3 = [ 7; 3 ]);; +assert (list_of_arbre_bin ab4 = [ 3; 5; 11; 7; 3 ]) + +(** +Insère un élément dans un arbre binaire de recherche. + +@param e l'élément à insérer +@param a l'arbre dans lequel on fait l'insersion +@return un arbre binaire de recherche contenant les éléments de a ainsi que e +*) +let rec insere_arbre_bin_recherche (e : int) (a : arbre_bin) : arbre_bin = + match a with + | ABVide -> ABNoeud (e, ABVide, ABVide) + | ABNoeud (x, fg, fd) -> + if e < x then ABNoeud (x, insere_arbre_bin_recherche e fg, fd) + else ABNoeud (x, fg, insere_arbre_bin_recherche e fd) + +let abr1 = insere_arbre_bin_recherche 7 ABVide +let abr2 = insere_arbre_bin_recherche 5 abr1 +let abr3 = insere_arbre_bin_recherche 3 abr2 +let abr4 = insere_arbre_bin_recherche 11 abr3;; + +assert (list_of_arbre_bin abr1 = [ 7 ]);; +assert (list_of_arbre_bin abr2 = [ 5; 7 ]);; +assert (list_of_arbre_bin abr3 = [ 3; 5; 7 ]);; +assert (list_of_arbre_bin abr4 = [ 3; 5; 7; 11 ]) + +(** +Créée un arbre binaire de recherche contenant les éléments de la liste + +@param l la liste contenant les éléments à placer dans l'arbre à créer +@return l'arbre binaire de recherche contenant les éléments de l +*) +let rec arbre_bin_rech_of_int_list (l : int list) : arbre_bin = + match l with + | [] -> ABVide + | x :: l' -> insere_arbre_bin_recherche x (arbre_bin_rech_of_int_list l') +;; + +assert (list_of_arbre_bin (arbre_bin_rech_of_int_list []) = []);; +assert (list_of_arbre_bin (arbre_bin_rech_of_int_list [ 3 ]) = [ 3 ]);; +assert (list_of_arbre_bin (arbre_bin_rech_of_int_list [ 3; 5 ]) = [ 3; 5 ]);; +assert (list_of_arbre_bin (arbre_bin_rech_of_int_list [ 5; 3 ]) = [ 3; 5 ]);; + +assert ( + list_of_arbre_bin (arbre_bin_rech_of_int_list [ 1; 2; 3; 4 ]) = [ 1; 2; 3; 4 ]) +;; + +assert ( + list_of_arbre_bin (arbre_bin_rech_of_int_list [ 4; 2; 1; 3 ]) = [ 1; 2; 3; 4 ]) + +(** +Trie une list d'int en utilisant un arbre binaire de recherche + +@param l la liste à trier +@return la liste triée +*) +let tri_abr (l : int list) : int list = + list_of_arbre_bin (arbre_bin_rech_of_int_list l) +;; + +assert (tri_abr [] = []);; +assert (tri_abr [ 3 ] = [ 3 ]);; +assert (tri_abr [ 3; 5 ] = [ 3; 5 ]);; +assert (tri_abr [ 5; 3 ] = [ 3; 5 ]);; +assert (tri_abr [ 1; 2; 3; 4 ] = [ 1; 2; 3; 4 ]);; +assert (tri_abr [ 4; 2; 1; 3 ] = [ 1; 2; 3; 4 ]) + +(**********************************************************************) +(* Expressions arithmétiques et variables *) +(**********************************************************************) + +(** +Type représentant les opérateurs binaires. +*) +type binop = Plus | Moins | Mult | Div + +(** +Expressions arithmétiques + let +*) +type expr = + | Cst of int + | Binop of binop * expr * expr + | Var of string + | Let of string * expr * expr + +(** affichage **) +let rec string_of_expr (e : expr) : string = + let string_of_binop (b : binop) = + match b with Plus -> " + " | Moins -> " - " | Mult -> " * " | Div -> " / " + in + + match e with + | Cst n -> string_of_int n + | Binop (op, l, r) -> + "(" ^ string_of_expr l ^ string_of_binop op ^ string_of_expr r ^ ")" + | Var x -> x + | Let (v, e1, e2) -> + "(let " ^ v ^ " = " ^ string_of_expr e1 ^ " in " ^ string_of_expr e2 ^ ")" + +(** Erreurs *) +type eval_err = DivZero | VarNonDef + +(** Résultats: int ou erreur *) +type resultat = Ok of int | Err of eval_err + +(** +Évalue une expression dans un environnement +*) +let rec eval_expr (e : expr) (env : (string * int) list) : resultat = + match e with + | Cst n -> Ok n + | Binop (op, e1, e2) -> ( + match (eval_expr e1 env, eval_expr e2 env) with + | Ok v1, Ok v2 -> ( + match op with + | Plus -> Ok (v1 + v2) + | Moins -> Ok (v1 - v2) + | Mult -> Ok (v1 * v2) + | Div -> if v2 = 0 then Err DivZero else Ok (v1 / v2)) + | Err e, _ -> Err e + | _, Err e -> Err e) + | Var x -> ( + match List.assoc_opt x env with None -> Err VarNonDef | Some n -> Ok n) + | Let (x, e1, e2) -> ( + match eval_expr e1 env with + | Ok v1 -> eval_expr e2 ((x, v1) :: env) + | Err e -> Err e) + +let e1 = Cst 3 +let e2 = Binop (Plus, Cst 3, Cst 5) +let e3 = Binop (Div, Cst 3, Cst 0) +let e4 = Let ("a", Cst 3, Binop (Moins, Var "a", Cst 3)) +let e5 = Let ("a", Cst 3, Var "b");; + +assert (eval_expr e1 [] = Ok 3);; +assert (eval_expr e2 [] = Ok 8);; +assert (eval_expr e3 [] = Err DivZero);; +assert (eval_expr e4 [] = Ok 0);; +assert (eval_expr e5 [] = Err VarNonDef);; +assert (eval_expr e5 [ ("b", 11) ] = Ok 11) + +(**********************************************************************) diff --git a/tp/tp4.ml b/tp/tp4.ml new file mode 100644 index 0000000000000000000000000000000000000000..a2e5a915965c6438cc5b713eca4585bed603d3cd --- /dev/null +++ b/tp/tp4.ml @@ -0,0 +1,240 @@ +(**********************************************************************) +(* Arbres n-aires *) +(**********************************************************************) +(** Arbre avec un nombre quelconque de fils *) +type 'a arbre_n = Feuille of 'a | Noeud of 'a arbre_n list + +let a1 = Feuille 1 +let a2 = Feuille 2 +let a3 = Noeud [] +let a4 = Noeud [ a1 ] +let a5 = Noeud [ a1; a2 ] +let a6 = Noeud [ a1; a2; a3; a4; a5 ] +let a_vide_1 = Noeud [] +let a_vide_2 = Noeud [ Noeud [] ] +(* Le type de ces arbres vide est 'a arbre_n. En effet, comme ces arbres ne + contiennent pas d'éléments ils peuvent être vus comme des arbresavec ce qu'on + veut comme type d'élément. *) + +let rec hauteur (a : 'a arbre_n) : int = + match a with Feuille _ -> 1 | Noeud l -> hauteur_foret l + 1 + +and hauteur_foret (l : 'arbre_n list) : int = + match l with + | [] -> 0 + | a :: l' -> max (hauteur a) (hauteur_foret l') +;; + +assert (hauteur a1 = 1);; +assert (hauteur a3 = 1);; +assert (hauteur a4 = 2);; +assert (hauteur a5 = 2);; +assert (hauteur a6 = 3) + +(** +Renvoie une liste contenant tous les éléments de l'arbre + +@param a: l'arbre +@return la liste de ses éléments +*) +let list_of_arbre (a : 'a arbre_n) : 'a list = + let rec list_of_arbre_aux (a : 'a arbre_n) (acc : 'a list) : 'a list + = + match a with + | Feuille x -> x :: acc + | Noeud f -> list_of_foret f acc + and list_of_foret (f : 'a arbre_n list) (acc : 'a list) : 'a list = + match f with + | [] -> acc + | a :: f' -> list_of_arbre_aux a (list_of_foret f' acc) + in + list_of_arbre_aux a [] +;; + +assert (list_of_arbre a1 = [ 1 ]);; +assert (list_of_arbre a4 = [ 1 ]);; +assert (list_of_arbre a5 = [ 1; 2 ]);; +assert (list_of_arbre a6 = [ 1; 2; 1; 1; 2 ]) + +(** +[minimum arbre] est le plus grand élément de arbre si arbre en contient au moins 1. + +@param arbre l'arbre dans lequel on cherche le minimum +@return None si l'arbre ne contient pas d'élément, ou sinon Some m avec m le plus grand élément de l'arbre +*) +let rec minimum (arbre : 'a arbre_n) : 'a option = + match arbre with + | Feuille x -> Some x + | Noeud la -> minimum_foret la + +(** +[minimum_foret l] donne l'élément minimal que l'on peut trouver dans une forêt + +@param l la forêt +@return None si la forêt ne contient pas d'élément ou sinon Some m où m est le plus grand élément de la forêt +*) +and minimum_foret (la : 'a arbre_n list) : 'a option = + match la with + | [] -> None + | a :: la' -> ( + match minimum_foret la' with + | None -> minimum a + | Some n -> ( + match minimum a with + | None -> Some n + | Some n' -> Some (min n n'))) +;; + +assert (minimum a1 = Some 1);; +assert (minimum a3 = None);; +assert (minimum a4 = Some 1);; +assert (minimum a5 = Some 1);; +assert (minimum a6 = Some 1) + +(** +[reduce f a] renvoie: +- None si a ne contient aucun élément +- Some x si a contient un seul élément x +- Some x où x est le résultat de la combinaison des éléments de a en utilisant f + +@param f la fonction de combinaison des éléments +@param a l'arbre qui contient les éléments +*) +let rec reduce (f : 'a -> 'a -> 'a) (arbre : 'a arbre_n) : 'a option = + match arbre with Feuille x -> Some x | Noeud l -> reduce_foret f l + +(** +[reduce_foret f l] renvoie: +- None si l (en tant que forêt) ne contient aucun élément +- Some x si l contient un seul élément x +- Some x où x est le résultat de la combinaison des éléments de l en utilisant f + +@param f la fonction de combinaison des éléments +@param la forêt qui contient les éléments +*) +and reduce_foret (f : 'a -> 'a -> 'a) (la : 'a arbre_n list) : + 'a option = + match la with + | [] -> None + | a :: la' -> ( + match reduce_foret f la' with + | None -> reduce f a + | Some n -> ( + match reduce f a with + | None -> Some n + | Some n' -> Some (f n n'))) +;; + +assert (reduce min a1 = Some 1);; +assert (reduce min a3 = None);; +assert (reduce min a4 = Some 1);; +assert (reduce min a5 = Some 1);; +assert (reduce min a6 = Some 1);; +assert (reduce ( + ) a1 = Some 1);; +assert (reduce ( + ) a3 = None);; +assert (reduce ( + ) a5 = Some 3);; +assert (reduce ( + ) a6 = Some 7) + +(**********************************************************************) +(* Files (FIFO) implémentées avec deux listes *) +(**********************************************************************) + +type 'a fifo = Fifo of ('a list * 'a list) + +(** File vide *) +let empty_fifo : 'a fifo = Fifo ([], []) + +(** +[push_fifo e f] Ajoute e dans f + +@param e l'élément a ajouter +@param f la fifo dans laquelle on veut ajouter l'élément +@return la fifo contenant les éléments de f puis e +*) +let push_fifo (e : 'a) (f : 'a fifo) : 'a fifo = + match f with Fifo (l1, l2) -> Fifo (e :: l1, l2) + +let f1 = push_fifo 1 empty_fifo +let f2 = push_fifo 2 f1 +let f3 = push_fifo 3 f2 +let f4 = push_fifo 4 f3;; + +assert (f1 = Fifo ([ 1 ], []));; +assert (f2 = Fifo ([ 2; 1 ], []));; +assert (f3 = Fifo ([ 3; 2; 1 ], []));; +assert (f4 = Fifo ([ 4; 3; 2; 1 ], [])) + +(** +[push_list_fifo l f] ajoute les éléments de l à la file f + +@param l les éléments à ajouter +@param f la file dans laquelle ajouter les éléments +@return la file contenant les éléments de f puis les éléments de l +*) +let rec push_list_fifo (l : 'a list) (f : 'a fifo) : 'a fifo = + match l with + | [] -> f + | x :: l' -> push_list_fifo l' (push_fifo x f) +;; + +assert (push_list_fifo [] empty_fifo = empty_fifo);; +assert (push_list_fifo [] f2 = f2);; +assert (push_list_fifo [ 1 ] empty_fifo = f1);; +assert (push_list_fifo [ 3; 4 ] f2 = f4);; +assert (push_list_fifo [ 1; 2; 3; 4 ] empty_fifo = f4) + +(** +Fonction utilitaire transférant tous les éléments de la liste de gauche dans +celle de droite en en renversant l'ordre au passage. +*) +let rec transfert_fifo (f : 'a fifo) : 'a fifo = + match f with + | Fifo ([], l2) -> Fifo ([], l2) + | Fifo (x :: l1, l2) -> transfert_fifo (Fifo (l1, x :: l2)) +;; + +assert (transfert_fifo f4 = Fifo ([], [ 1; 2; 3; 4 ]));; +assert (transfert_fifo f1 = Fifo ([], [ 1 ])) + +(** +[pop_fifo f] renvoie le premier élément de f s'il y en a un, ainsi que la file contenant le reste des éléments de f. + +@param f la file dans laquelle on veut prendre un élément +@return (f',r) où +- f' est la file contenant les éléments de f sauf le premier +- r est Some x si f a pour premier élément x ou bien None si f est vide +*) +let pop_fifo (f : 'a fifo) : 'a fifo * 'a option = + match f with + | Fifo (l1, []) -> ( + match transfert_fifo f with + | Fifo (_, []) -> (Fifo ([], []), None) + | Fifo (_, x :: l2') -> (Fifo ([], l2'), Some x)) + | Fifo (l1, x :: l2') -> (Fifo (l1, l2'), Some x) +;; + +assert (pop_fifo empty_fifo = (empty_fifo, None));; +assert (pop_fifo f1 = (empty_fifo, Some 1));; +assert (pop_fifo f2 = (Fifo ([], [ 2 ]), Some 1));; +assert (pop_fifo (fst (pop_fifo f2)) = (empty_fifo, Some 2)) + +(** +Renvoie tous les éléments de la file dans l'ordre de celle-ci + +@param f la file dont on veut les éléments +@return une liste contenant les éléments de f dans l'ordre +*) +let rec pop_all_fifo (f : 'a fifo) : 'a list = + match pop_fifo f with + | _, None -> [] + | f', Some x -> x :: pop_all_fifo f' +;; + +assert (pop_all_fifo empty_fifo = []);; +assert (pop_all_fifo f1 = [ 1 ]);; +assert (pop_all_fifo f4 = [ 1; 2; 3; 4 ]);; + +(* Un test mélangeant les opérations de push et de pop de la file *) +assert ( + pop_all_fifo (push_list_fifo [ 3; 4 ] (fst (pop_fifo f2))) + = [ 2; 3; 4 ]) diff --git a/tp/tp5.md b/tp/tp5.md new file mode 100644 index 0000000000000000000000000000000000000000..6dd56495d4a9d7dd1684407e30641eeecf214298 --- /dev/null +++ b/tp/tp5.md @@ -0,0 +1,221 @@ +# TP5 + +## 1. Arbre n-aires: recodage + +Dans cet exercice, on reprend les fonctionnalités développées dans la section 1. du [TP4](tp4.md), mais en les recodant avec les fonctions déjà fournies avec OCaml. + +Remarque: en OCaml on peut écrire `fun x y -> ...` à la place de `fun x -> fun y -> ...`. + +### 1.1. Recodage de quelques fonctions de base avec la bibliothèque standard OCaml + +> Reprendre le type `'a arbre_n` du TP4, section 1.1 + +On pourra reprendre les tests écrits lors du TP4. + +> Recoder la fonction `hauteur_arbre` sans utiliser `hauteur_foret`, mais en appelant directement `List.map` et `List.fold_left` pour extraire les hauteurs des arbres fils et trouver la hauteur maximale parmi celles-ci. + +> Recoder `list_of_arbre_aux` en utilisant `List.fold_right` pour remplacer les appels à `list_of_foret`. +> Encapsuler `list_of_arbre_aux` avec un `let ... in ...` dans la définition de `list_of_arbre`. +> On écrira le `let` avant de prendre l'argument de `list_of_foret`. + +### 1.2 Gestion d'option, fold et minimum + +Pour gérer proprement le calcul du minimum, on va s'équiper d'un décorateur `lift_option_2` ayant le comportement suivant. Soit `f: 'a -> 'a -> 'a`. On suppose que `g = lift_option_2 f`. Alors `g` aura le type `'a option -> 'a -> 'a option`. Si le premier argument de `g` est `None`, alors `g` renvoie `Some x` si `x` est son second argument. Sinon `g` renvoie `Some (f y x)` où `y` est la valeur contenue dans son premier argument et `x` est son second argument. + +> Définir `lift_option_2` en commençant par préciser son type. Tester avec `assert` en passant la fonction `min` à `lift_option_2`. + +On veut à présent définir `fold_left_arbre` qui aggrège une valeur via un accumulateur à la manière de `List.fold_left`, mais en parcourant un arbre et pas une liste. + +> Définir le type, puis coder `fold_left_arbre`. On pourra utiliser intelligement `List.fold_left` pour gérer le cas des `Noeud`. +> Tester en calculant la somme des éléments d'arbres bien choisis. + +On peut remarquer que `reduce` du TP4 ressemble énormément à `fold_left_arbre`: les deux vont parcourir les éléments de l'arbre les combinant via un accumulateur. Elles diffèrent cependant sur les points suivants: + +- `reduce` prend en argument une fonction de type `'a -> 'a -> 'a` alors que `fold_left_arbre` prend une fonction un peu plus générique de type `'b -> 'a -> 'b`. +- `reduce` renvoie forcément une `option` alors que `fold_left_arbre` renvoie uniquement une option si `'b` est lui-même une `option`. + +> En utilisant intelligement `fold_left_arbre`, recoder `reduce`. + +> Recoder `list_of_arbre` en utilisant `fold_left_arbre`. + +## 2. Application et compilation séparée + +On souhaite implémenter une application de gestion de la fabrication des jouets de Noël par les lutins du Père-Noël. +Bien que l'application soit modeste on souhaite pouyvoir la faire grossir selon les besoins. +On va donc dès le départ la diviser en différents modules, le code de l'application contenant simplement des appels aux bonnes fonctions des autres modules. + +Les différentes parties de l'application intiale sont les suivantes: + +- Un module `Association` de gestion des associations clé-valeur basé sur des arbres binaires de recherche. On veut pouvoir, à terme (mais pas dans ce TP), remplacer les usages de ce module par un module de la bibliothèque standard OCaml. +- Un module `Usine` contenant les types liés au métier de l'application: lutins, jouets, etc. Ce module contiendra également les différentes fonctions utilisées pour gérer l'usine. +- Un module `LutinsApp` qui va contenir le code de gestion des arguments en ligne de commande + +On peut résumé les dépendances simples des modules de cette application via le diagrame suivant (la flèche signifie "est utilisé par"): + +```mermaid +graph TD + Association --> Usine + Usine --> LutinsApp + LutinsApp --> main.ml +``` + +### 2.1. Mise en place d'un projet `dune` + +**Remarque:** Si vous utilisez votre propre machine, il faut installer `opam` et `dune`. Voir la [documentation opam](https://opam.ocaml.org/doc/Install.html) et la [documentation dune](https://dune.build/install). Cette installation est déjà faite pour les machines des salles TP, mais il faut bien avoir effectué la configuration de votre compte ([cf doc](../CONFIGURATION.md)). + +> Créer une nouveau projet dune intitulé `lutins` via la commande suivante: +> +> ``` +> dune init project lutins +> ``` + +La commande va créer un répertoire `lutins`. Ce répertoire contiendra un ou plusieurs sous-répertoires nommés `_build` qui vont contenir les défférents fichiers générés par les outils de compilation OCaml. + +> Lister les fichiers et les répertoires générés par la commande `dune init`. + +> Lancer la commande `dune exec lutins` depuis le répertoire du projet. Dans quel fichier se trouve le code exécuté ? + +> Ajouter le code suivant dans le fichier `test/lutins.ml`: +> +> ```ocaml +> assert (2=1) +> ``` +> +> puis lancer la commande `dune test`. +> Constater l'erreur, puis supprimer cette ligne de test. + +Pour finir créer à la racine du projet un fichier `.ocamlformat` avec le contenu suivant: + +``` +profile = default +margin = 70 +``` + +Si vous avez installé `ocamlformat` (via `opam` sur votre machine, il sera préinstallé en salle TP), cela permettra de reformatter le code (c.-à -d. réarranger la présentation). + +### 2.2. Premier module + +Créer deux fichiers dans le répertoire `lib`. Le premier, nommé `usine.ml` contiendra le code du module `Usine`. Le second, `usine.mli` contiendra les déclarations de type et de fonction pour les modules et les fichiers qui utiliseront `Usine`. + +> Dans le fichier `usine.ml`, définir un type somme `jour` pour représenter les jours de la semaine (un jour par constructeur). +> Définir également une fonction `string_of_jour` permettant d'obtenir la `string` représentant le jour passé en argument. + +Le fichier `usine.ml` va ainsi contenir l'**implémentation** du module `Usine`. + +On veut maintenant indiquer que ces deux déclarations sont disponibles aux autres modules. Pour cela ajouter le code suivant à l'autre fichier, c'est-à -dire `usine.mli`: + +```ocaml +(** +Les jours de la semaine. +*) +type jour = Lundi | Mardi + +(** +Donne une représentation sous forme de string d'un jour. +*) +val string_of_jour: jour -> string +``` + +Noter le commentaire de documentation, qui devient plus important ici car le code n'est pas accessible en dehors du module `Usine`. + +`usine.mli` contient l'**interface** du module `Usine`. + +### 2.3. Utilisation du module `Usine` + +On souhaite maintenant utiliser ce module. Pour cela on va d'abord vérifier que la ligne suivante se trouve dans le fichier `bin/dune` (on l'ajoutera si elle est manquante): + +``` + (libraries lutins) +``` + +le contenu du fichier doit ressembler à ce qui suit: + +``` +(executable + (public_name lutins) + (name main) + (libraries lutins)) +``` + +Le module `Usine` est maintenant disponible, pour utiliser ses types et ses fonctions depuis `main.ml`, il faudra les précéder de `Lutins.Usine`, par exemple pour utiliser le constructeur `Lundi`, on écrira `Lutins.Usine.Lundi`. + +> Modifier le code de `main.ml` afin afficher en plus `Nous sommes ` suivi d'un jour de la semaine traduit en string via un appel à `string_of_jour`. + +> Vérifier le bon fonctionnement du programme en lançant `dune exec lutins`. Dune va recompiler le programme et la bibliothèque (c'est-à -dire le code dans `lib` avant de lancer l'exécution du code). + +On peut constater que l'usage de `Lutins.Usine` va vite devenir lourd. +Pour éviter ce problème, on peut utiliser la directive `open Lutins.Usine;;` en début de fichier. Le compilateur va ensuite directement chercher dans le module `Lutins.Usine` les définitions de `jour` et `string_of_jour` sans que l'on ait besoin de les précéder de `Lutins.Usine.`. + +Enfin on veut pouvoir utiliser le système de test de dune avec le nouveau module `Lutins.Usine`. Pour cela, il suffit d'ajouter dans le fichier `test/dune` une déclaration `libraries` similaire à ce qui est fait dans `bin/dune`: + +``` +(test + (name lutins) + (libraries lutins)) +``` + +Ensuite on peut utiliser, et donc tester, les types et les fonctions déclarées dans `usine.mli` comme cela a été fait dans le `main.ml`. + +**Remarque:** Il se peut l'extension OCaml Platform de VSCode ait parfois du mal à se mettre à jour avec les nouvelles définitions. Si cela compile correctement avec `dune`, mais que VSCode indique des erreurs, c'est `dune` qui a raison. + +> Ajouter des `assert` pour tester `string_of_jour`. Lancer `dune test` pour vérifier que tous vos tests passent correctement. + +### 2.4. Masquer des définitions + +On veut maintenant définir au sein de l'usine de fabrication de jouets une notion de _configuration_ de la journée. La configuration d'une journée permet de connaître deux choses: pour chaque lutin, le jouet qu'il va fabriquer et pour chaque lutin et chaque jouet la quantité qu'il peut fabriquer. Les jouets et les lutins étant représentés par des chaînes de caractères, il se peut qu'un lutin ou un jouet passé en argument n'existe pas. On utilise donc une `option` pour gérer cette situation. + +On va maintenant créer dans le module `Usine` un type `configuration` qui sera simplement une paire de fonctions. La première aura le type `string -> string option`, la seconde aura le type `string -> string -> int option`. + +Définir ce type dans `usine.ml`: + +```ocaml +type configuration = (string -> option string) * (string -> string -> option int);; +``` + +Dans `usine.mli` on va en revanche, on va masquer les détails de la définition et juste conserver le nom du type: + +```ocaml +type configuration;; +``` + +Tel quel, on ne peut pas manipuler les valeurs de ce type à l'extérieur du module `Usine`. On va donc lui ajouter des fonctions pour créer des configurations et en extraire les différentes parties. + +Ajouter les fonctions suivantes, en les déclarant dans l'interface du module `Usine` + +- `mk_configuration: (string -> option string) -> (string -> string -> int option) -> configuration` cette fonction va fabriquer une paire avec ses deux arguments. +- `get_jouet: configuration -> (string -> string option)` cette fonction va extraire le premier élément de la paire +- `get_nb_jouets: configuration -> (string -> string -> int option)` cette fonction va extraire le deuxième élément de la paire + +Tester ces fonctions dans `test/lutins.ml` avec une fonction qui renvoie toujours "toupie" pour le choix du jouet et toujours `42` pour le nombre de jouets. + +### 2.5. Codage de l'application de gestion des jouets - début + +On dispose à présent des éléments de langage pour pouvoir coder le début de l'application de gestion de l'usine de jouets. + +Ajouter au module `Usine` une fonction `jour_of_string` qui prend une `string` et renvoie une option de `jour` avec `Some` du bon jour si la chaîne représente bien un jour et `None` sinon. + +Créer un module `Association` contenant le code pour gérer des associations clé-valeur. Il contiendra: + +- un type générique `('a,'b) assoc_t` dont l'implémentation est cachée; +- une fonction `put: 'a -> 'b -> ('a, 'b) assoc_t -> ('a, 'b) assoc_t` qui associe une clé (de type `'a`) à une valeur (de type `'b`) dans une structure d'association; +- une fonction `get: 'a -> ('a, 'b) assoc_t -> 'b option` qui renvoie une valeur associée à une clé, si elle existe; +- une constante `empty: ('a,'b) assoc_t` correspondant à l'association vide. + +L'implémentation est laissée libre. On peut par exemple utiliser des listes de paires clé-valeur ou bien des arbres de recherche basés sur la comparaison générique `(<)` prédéfinie dans OCaml. + +Dans le module `Usine`, utiliser cette structure pour créer une variable globale pour y stocker, pour chaque jour, une configuration. Essayer de faire varier les fonctions de la configuration selon le jour. + +Créer également une variable globale contenant la liste des noms des lutins. + +Créer une fonction `calcule_jouets_config: configuration -> (string,int) list` qui indique pour chaque jouet combien d'exemplaires ont été fabriqué, en excluant les jouets fabriqués zéro fois. + +Enfin créer un dernier module `LutinsApp` qui contiendra: + +- une fonction `affiche_jouets: (string,int) list -> string` qui calculera une chaîne d'affichage de la sortie de la fonction `calcule_jouets_config`; +- une fonction `run: string list -> unit` qui prendra une liste de string dont le premier élément représente un jour et affichera (via `print_endline`) les jouets produits ce jour-là . + +`main.ml` appelera `run` en lui passant la liste des arguments en ligne de commande (on pourra utiliser `Sys.argv` et `Array.to_list` pour récupérer les arguments et les transformer en `string list` pour être traités par `run`). + +Il sera utile de consulter les modules de la biliothèque standard de OCaml ([lien pour la version 4](https://v2.ocaml.org/releases/4.14/api/index.html) utilisée en TP). + +Une version plus évoluée de cette application sera codée dans les TPs ultérieurs.