# TP7 - Application et configuration par fonctions On va implémenter une petite application simple en OCaml. Même si l'application tiendra en un seul fichier, elle comprendra plusieurs parties regroupées en _modules_. ## 1. Application et modules 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 pouvoir 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é d'abord sur des listes, puis 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 - Enfin le code hors de ces modules lancera simplement l'application. Tous ces modules seront placés dans le fichier `usinelutins.ml`. On peut résumer les dépendances simples des modules de cette application via le diagramme suivant (la flèche signifie "est utilisé par"): ```mermaid graph TD A[Association] --> B[Usine] B --> C[LutinsApp] C --> D[code directement dans usinelutins.ml] ``` ### 2. Premiers modules Créer un fichier `usinelutins.ml`. À titre d'exemple de module on donne le code suivant, à placer au début du fichier `usinelutins.ml`: ```ocaml module Association = struct (* Type pour représenter des dictionnaires (ou map ou association) dont la clé est une string *) type 'a t = (string * 'a) list (* Fonction pour chercher dans un dictionnaire. get d k renvoie None si k n'est pas une clé de d, Some x si x est la valeur associée à k dans d. *) let get : 'a t -> string -> 'a option = fun d k -> List.assoc_opt k d (* Ajoute un couple clé/valeur dans un dictionnaire, si la clé est déjà dans le dictionnaire, change en v la valeur qui sera renvoyée par get *) let put : 'a t -> string -> 'a -> 'a t = fun d k v -> (k, v) :: d (* Le dictionnaire vide *) let empty : 'a t = [] (* Supprime les valeurs associées à la clé k dans le dictionnaire. *) let rec delete : 'a t -> string -> 'a t = fun d k -> match d with | [] -> [] | (k', v') :: rest -> if k = k' then delete rest k else (k', v') :: delete rest k end ``` Créer un module `Usine` à la suite du module `Association`. Dans ce module, créer: - un type `jour` pour représenter les jours de la semaine (`Lundi`, `Mardi`, etc) - une fonction `string_of_jour: jour -> string` - une fonction `jour_opt_of_string: string -> jour option` > Ajouter des `assert` pour tester `string_of_jour` et `jour_opt_of_string`. ### 3. Utilisation du module `Usine` On souhaite maintenant utiliser ce module. Pour cela il faut: 1. Écrire le code qui l'utilise après la définition du module 2. Faire précéder le nom d'une fonction, d'un type ou d'un constructeur par le nom du module, par exemple on écrira `Usine.Lundi`. Ajouter le code suivant à la fin de `usinelutins.ml`: ```ocaml let usage () = print_endline "\nUsage: ocaml usinelutins.ml jour" let run args = match args with | _pgm :: jour_s :: _ -> print_endline "Bonjour, nous sommes un certain jour" | _ -> usage () let _ = run (Array.to_list Sys.argv) ``` > Modifier ce code 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 `ocaml usinelutins.ml`. Modifier le code de façon à ce que le jour choisi soit obtenu à partir de `jour_s`. Gérer par un affichage approprié le cas ou `jour_s` n'est pas un jour. ### 4. Enrichir le module `Usine` 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 le module `Usine`: ```ocaml type configuration = (string -> string option) * (string -> string -> int option);; ``` Ajouter les fonctions suivantes dans le module `Usine`: - `mk_configuration: (string -> string option) -> (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 avec des `assert` en utilisant une fonction qui renvoie toujours "toupie" pour le choix du jouet et toujours `42` pour le nombre de jouets. ### 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. Dans le module `Usine`, créer une configuration basée sur des `assoc` 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`; Modifier la fonction `run: string list -> unit` pour affichera (via `print_endline`) les jouets produits durant le jour passé en argument. Avec l'implémentation actuelle, il est possible que certains jouets s'affichent plusieurs fois, cela sera corrigé avec la section suivante. ### 6. Optimisation du module Association Changer le type et le code du module `Association` de façon à utiliser des arbres binaires de recherche à la place de listes de paires. Le code du reste de l'application ne devrait pas changer.