Skip to content
Snippets Groups Projects
Commit b44a9032 authored by COQUERY EMMANUEL's avatar COQUERY EMMANUEL
Browse files

Mise à jour TP5

parent 5391de9b
No related branches found
No related tags found
No related merge requests found
# TP5: Parcours génériques de structure et projets dune # TP5: Parcours génériques de structure et projets dune
## 1. Arbre n-aires: recodage ## 1. Manipulations de listes à l'aide de fonctions génériques
On considère les fonctions suivantes prédéfinies en OCaml:
- **`List.map: ('a -> 'b) -> 'a list -> 'b list`** Cette fonction transforme une liste en utilisant la fonction passée en paramètre pour transformer les éléments un par un. Ainsi `List.map f [x1; x2; x3; ... ]` renverra une liste égale à `[f x1; f x2; f x3; ... ]`.
- **`List.filter: ('a -> bool) -> 'a list -> 'a list`** Cette fonction transforme une liste en utilisant la fonction passée en paramètre conserver uniquement certains éléments. Ainsi `List.map f [x1; x2; x3; ... ]` renverra une liste contenant exatctement les `xi` pour lesquels `f xi` vaut `true`.
- **`List.for_all: ('a -> bool) -> 'a list -> bool`** Cette fonction indique si une fonction renvoie vrai pour tous les éléments d'une liste. Ainsi `List.for_all f [x1; x2; x3; ... ]` renvoie `true` si pour tous les `xi`, on a `f xi` vaut `true`.
## 1.1. Utilisation des fonctions sur les listes
En utilisant `List.map`, coder les fonctions suivantes:
- Une fonction `f1` qui prend une liste d'`int` et renvoie la liste de leur représentation sous forme de `string`.
- Une fonction `f2` qui prend un `int` `x` et une liste d'`int` `l` et renvoie la liste transformée en multipliant chaque élément de `l` par `x`.
- Une fonction `f3` qui prend deux `int` `x` et `y` et une liste d'`int` `l` et renvoie la liste (de `bool`) indiquant pour chacun des éléments de `l` s'il est bien compris entre `x` et `y`.
Bien tester vos fonctions avec `assert`.
En utilisant `List.filter`, coder les fonctions suivantes:
- Une fonction `f4` qui prend deux `int` `x` et `y` et une liste d'`int` `l` et renvoie la liste des éléments de `l` qui sont compris entre `x` et `y`.
- Une fonction `f5` qui prend une liste de liste d'`int` et renvoie la liste sans les (sous-)listes vides.
Bien tester vos fonctions avec `assert`.
En OCaml, on peut voir une opérateur comme une fonction en le plaçant tout seul entre parenthèses. Ainsi `(!=)` est une fonction qui prend deux arguments et renvoie `true` si il sont égaux. Ainsi `(!=) 1 2` revoie `true`, alors que `(!=) 1 1` renvoie `false`.
Recoder la fonction `f5` sans le mot clé `fun` en utilisant une application partielle de `(!=)`.
On peut bien sûr combiner `List.map` et `List.filter`.
Coder une fonction `f6` qui prend une liste d'`int option` `l` et renvoie une liste d'`int` contenant les valeurs `int` contenues dans `l`. Par exemple, `f6 [ Some 3; None; Some 18; Some 42; None; Some 37; None]` vaudra `[3; 18; 42; 37]`.
## 1.2 Recodage de fonctions sur les listes
Recoder `List.map`, `List.filter` et `List.for_all`.
## 2. 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. 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 -> ...`. 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 ### 2.1. Recodage de quelques fonctions de base avec la bibliothèque standard OCaml
> Reprendre le type `'a arbre_n` du TP4, section 1.1 > Reprendre le type `'a arbre_n` du TP4, section 1.1
...@@ -18,7 +55,7 @@ On pourra reprendre les tests écrits lors du TP4. ...@@ -18,7 +55,7 @@ On pourra reprendre les tests écrits lors du TP4.
> Encapsuler `list_of_arbre_aux` avec un `let ... in ...` dans la définition de `list_of_arbre`. > 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_arbre`. > On écrira le `let` avant de prendre l'argument de `list_of_arbre`.
### 1.2 Gestion d'option, fold et minimum ### 2.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)``y` est la valeur contenue dans son premier argument et `x` est son second argument. 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)``y` est la valeur contenue dans son premier argument et `x` est son second argument.
...@@ -37,187 +74,3 @@ On peut remarquer que `reduce` du TP4 ressemble énormément à `fold_left_arbre ...@@ -37,187 +74,3 @@ On peut remarquer que `reduce` du TP4 ressemble énormément à `fold_left_arbre
> En utilisant intelligement `fold_left_arbre`, recoder `reduce`. > En utilisant intelligement `fold_left_arbre`, recoder `reduce`.
> Recoder `list_of_arbre` en utilisant `fold_left_arbre`. > 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 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é 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 diagramme 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)).
**Attention:** un problème se pose avec l'utilisation sur les machines TP. Les commandes `dune` se bloquent. Il faut impérativement travailler dans un répertoire propre à la machine, par exemple `/tmp/p1234567``p1234567` est votre login étudiant. Il vous faudra versionner votre projet dune sur https://forge.univ-lyon1.fr, ou à défaut vous envoyer le .zip du projet. Attention à exclure les répertoires `_build`.
> 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 -> string option) * (string -> string -> int option);;
```
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 -> 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 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.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment