-
COQUERY EMMANUEL authoredCOQUERY EMMANUEL authored
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"):
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
:
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 testerstring_of_jour
etjour_opt_of_string
.
Usine
3. Utilisation du module On souhaite maintenant utiliser ce module. Pour cela il faut:
- Écrire le code qui l'utilise après la définition du module
- 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
:
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 dejour_s
. Gérer par un affichage approprié le cas oujour_s
n'est pas un jour.
Usine
4. Enrichir le module 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
:
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 fonctioncalcule_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.