cnam:utc503:td3

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentes Révision précédente
Prochaine révision
Révision précédente
cnam:utc503:td3 [2026/10/20 02:09] – [3.3 - Rendre transparent] jcheroncnam:utc503:td3 [2026/10/20 09:17] (Version actuelle) – jcheron
Ligne 1: Ligne 1:
-====== TD 3 : Fondamentaux de la programmation fonctionnelle en JavaScript ======+====== TD 3 : Pf en JS ======
  
 <WRAP info round> <WRAP info round>
Ligne 21: Ligne 21:
 | ''push'', ''pop'', ''shift'', ''unshift'', ''splice'', ''sort'', ''reverse'', ''fill'' | ''map'', ''filter'', ''reduce'', ''slice'', ''concat'', ''[...arr]'', ''toSorted'', ''toReversed'', ''with'' | | ''push'', ''pop'', ''shift'', ''unshift'', ''splice'', ''sort'', ''reverse'', ''fill'' | ''map'', ''filter'', ''reduce'', ''slice'', ''concat'', ''[...arr]'', ''toSorted'', ''toReversed'', ''with'' |
  
-Gardez ce tableau sous les yeux : ''sort'' et ''reverse'' sont des pièges classiques.+''sort'' et ''reverse'' sont des pièges classiques.
 </WRAP> </WRAP>
  
Ligne 30: Ligne 30:
 ==== 1.1 - Écrire une fonction pure ==== ==== 1.1 - Écrire une fonction pure ====
  
-  * Écrire ''double(numbers)'' qui retourne un **nouveau** tableau où chaque nombre est doublé. **Interdiction** d'utiliser ''for'', ''while'' ou ''forEach'' : utilisez ''map''.+  * Écrire ''double(numbers)'' qui retourne un **nouveau** tableau où chaque nombre est doublé. 
   * Écrire les tests suivants :   * Écrire les tests suivants :
  
Ligne 76: Ligne 76:
 </sxh> </sxh>
  
-<WRAP tip round> 
-**Indice pour D** : une fonction pure ne peut pas "aller chercher" l'heure. Que faut-il changer dans sa signature ? 
-</WRAP> 
  
 ==== 1.4 - Pour aller plus loin : l'impureté cachée ==== ==== 1.4 - Pour aller plus loin : l'impureté cachée ====
Ligne 186: Ligne 183:
 ===== Exercice 3 : Transparence référentielle ===== ===== Exercice 3 : Transparence référentielle =====
  
-**Définition à tester** : une expression est référentiellement transparente si on peut la remplacer par sa valeur sans changer le comportement du programme.+**Définition** : une expression est référentiellement transparente si on peut la remplacer par sa valeur sans changer le comportement du programme.
  
-==== 3.1 - Substitution manuelle ====+==== 3.1 - Substitution sur des fonctions simples ====
  
-Soit :+  * Écrire %%add(a, b)%% et %%mul(a, b)%%. 
 +  * Soit l'expression suivante : 
 + 
 +<sxh javascript> 
 +add(mul(2, 3), mul(2, 3)) 
 +</sxh> 
 + 
 +  * Remplacer **à la main**, étape par étape, chaque appel par sa valeur (modèle de substitution) et vérifier que le résultat ne change pas. 
 +  * Factoriser :
  
 <sxh javascript> <sxh javascript>
-const add = (a, b) => a + b; +const x = mul(2, 3); 
-const mul = (a, b) => a * b; +add(x, x)
-const result = add(mul(2, 3), mul(4, 5)) + add(mul(2, 3), 1);+
 </sxh> </sxh>
  
-  * Faire l'**évaluation par substitution** étape par étape, à la main, en écrivant chaque ligne de réécriture jusqu'à la valeur finale. +  * Pourquoi cette factorisation est-elle sûre ?
-  * Identifier les sous-expressions répétées. Que pouvez-vous en faire (extraire dans une constante) ? Pourquoi cette factorisation est-elle sûre ici ?+
  
-==== 3.2 - Substitution qui casse ====+==== 3.2 - Détecter les ruptures de transparence ====
  
-Pour chaque extrait, remplacez l'appel par sa valeur retournée et **dites si le comportement du programme change**. Justifiez par ce qui est perdu lors de la substitution.+Pour chacune des fonctions suivantes, dire si elle est transparente et justifier. Tester en appelant deux fois de suite avec les mêmes arguments.
  
 <sxh javascript> <sxh javascript>
Ligne 209: Ligne 212:
 let counter = 0; let counter = 0;
 const next = () => ++counter; const next = () => ++counter;
-const x = next() + next(); 
  
 // B // B
 const rand = () => Math.random(); const rand = () => Math.random();
-const y = rand() === rand(); 
  
 // C // C
-const log = (s) => { console.log(s); return s; }; +const now = () => Date.now();
-const z = log("a") + log("a");+
  
-// D +// D (TAX est modifiable ailleurs dans le programme) 
-const getUser = (id) => fetch(`/users/${id}`);   // asynchrone, réseau+let TAX = 0.2; 
 +const addTax = (price) => price * (1 + TAX); 
 + 
 +// E 
 +const first = (arr) => arr[0]; 
 + 
 +// F 
 +const logAndAdd = (a, b) => { 
 +  console.log(a); 
 +  return a + b; 
 +};
 </sxh> </sxh>
  
Ligne 227: Ligne 237:
 ==== 3.3 - Rendre transparent ==== ==== 3.3 - Rendre transparent ====
  
-  * Réécrivez A pour qu'il soit référentiellement transparent (indice : l'état doit devenir une entrée et une sortie). +  * Réécrivez **A** pour qu'il soit référentiellement transparent (indice : l'état doit devenir une entrée //et// une sortie). 
-  * Réécrivez B pour rendre le hasard déterministe : ''random(seed)'' retourne un couple ''[valeur, nouvelleGraine]'' (générateur congruentiel linéaire simple, par ex. (seed * 1664525 + 1013904223) % 2**32). Montrez que ''random(42)'' donne toujours le même résultat.+  * Réécrivez **B** pour rendre le hasard déterministe : %%random(seed)%% retourne un couple %%[valeur, nouvelleGraine]%% (générateur congruentiel linéaire simple, par exemple : 
 + 
 +<sxh javascript> 
 +(seed * 1664525 + 1013904223) % 2**32 
 +</sxh> 
 + 
 +  * Montrez que %%random(42)%% donne toujours le même résultat.
  
 ==== 3.4 - Mémoïsation : l'application directe ==== ==== 3.4 - Mémoïsation : l'application directe ====
  
-  * Écrire ''memoize(fn)'' qui retourne une version de ''fn'' mettant en cache ses résultats (clé : ''JSON.stringify(args)''). +  * Écrire %%memoize(fn)%% qui retourne une version de %%fn%% mettant en cache ses résultats (clé : %%JSON.stringify(args)%%). 
-  * Appliquer à une fonction coûteuse (ex : ''fib'' récursif naïf, comparez les temps pour ''fib(35)''). +  * Appliquer à une fonction coûteuse (ex : %%fib%% récursif naïf, comparez les temps pour %%fib(35)%%). 
-  * **Test crucial** : appliquer ''memoize'' à la fonction ''next'' de 3.2.A et à ''rand'' de 3.2.B. Que se passe-t-il ? Pourquoi est-ce incorrect ?+  * **Test crucial** : appliquer %%memoize%% à la fonction %%next%% de 3.2.A et à %%rand%% de 3.2.B. Que se passe-t-il ? Pourquoi est-ce incorrect ?
  
 +<WRAP todo round>
 +=== Points de discussion ===
 +
 +  * Pourquoi la mémoïsation n'est-elle //valide// que sur des fonctions référentiellement transparentes ? Lien avec 3.4.
 +  * En 3.1, la factorisation de %%mul(2, 3)%% est sûre. Dans un code avec effets de bord, serait-elle toujours sûre ? Quelle conséquence pour le //refactoring// et pour les optimisations d'un compilateur ?
 +  * Une fonction peut être pure sans que son //appel// soit "inoffensif" (ex : très coûteuse). La transparence référentielle garantit-elle les performances ?
 +  * Une fonction qui lit une constante globale (%%const TAX = 0.2%%) est-elle référentiellement transparente ? Et si la constante est un objet mutable ?
 +</WRAP>
  
 ===== Exercice 4 : Fonctions d'ordre supérieur ===== ===== Exercice 4 : Fonctions d'ordre supérieur =====
Ligne 245: Ligne 269:
 Sans utiliser les méthodes natives correspondantes, implémenter (avec une boucle ou de la récursion) : Sans utiliser les méthodes natives correspondantes, implémenter (avec une boucle ou de la récursion) :
  
-  * ''myMap(arr, fn)'' ; +  * %%myMap(arr, fn)%% ; 
-  * ''myFilter(arr, pred)'' ; +  * %%myFilter(arr, pred)%% ; 
-  * ''myReduce(arr, fn, init)''.+  * %%myReduce(arr, fn, init)%%.
  
-Puis **réimplémenter ''myMap'' et ''myFilter'' uniquement à partir de ''myReduce''**. Ce que vous découvrez : ''reduce'' est la primitive universelle de traitement de tableau.+Puis **réimplémenter %%myMap%% et %%myFilter%% uniquement à partir de %%myReduce%%**. Ce que vous découvrez : %%reduce%% est la primitive universelle de traitement de tableau.
  
 ==== 4.2 - Fonctions qui retournent des fonctions (closures) ==== ==== 4.2 - Fonctions qui retournent des fonctions (closures) ====
  
-  * ''multiplier(n)'' retourne une fonction qui multiplie son argument par ''n''. Utiliser pour créer ''double'', ''triple'' et les passer à ''myMap''. +  * %%multiplier(n)%% retourne une fonction qui multiplie son argument par %%n%%. Utiliser pour créer %%double%%, %%triple%% et les passer à %%myMap%%. 
-  * ''greaterThan(n)'' retourne un prédicat. L'utiliser avec ''filter''. +  * %%greaterThan(n)%% retourne un prédicat. L'utiliser avec %%filter%%. 
-  * ''counter()'' retourne une fonction qui retourne 1, 2, 3... à chaque appel. **Cette fonction est-elle pure ?** Où est stocké l'état ? Que dire de ''counter'' elle-même (celle qui crée) ?+  * %%counter()%% retourne une fonction qui retourne 1, 2, 3... à chaque appel. **Cette fonction est-elle pure ?** Où est stocké l'état ? Que dire de %%counter%% elle-même (celle qui crée) ?
  
 ==== 4.3 - Currying et application partielle ==== ==== 4.3 - Currying et application partielle ====
  
-  * Écrire ''curry(fn)'' qui transforme ''f(a, b, c)'' en ''f(a)(b)(c)'' (à arité fixe, via ''fn.length''). +  * Écrire %%curry(fn)%% qui transforme %%f(a, b, c)%% en %%f(a)(b)(c)%% (à arité fixe, via %%fn.length%%). 
-  * Écrire ''partial(fn, ...args)'' qui fige les premiers arguments. +  * Écrire %%partial(fn, ...args)%% qui fige les premiers arguments. 
-  * Utiliser sur : ''const add3 = (a, b, c) => a + b + c;'' et sur ''const formatPrice = (currency, decimals, value) => ...''. +  * Utiliser sur les fonctions suivantes : 
-  * Créer ''formatEUR'' et ''formatUSD2'' à partir de ''formatPrice''.+ 
 +<sxh javascript> 
 +const add3 = (a, b, c) => a + b + c; 
 +const formatPrice = (currency, decimals, value) => /* à compléter */; 
 +</sxh> 
 + 
 +  * Créer %%formatEUR%% et %%formatUSD2%% à partir de %%formatPrice%%.
  
 ==== 4.4 - Composition ==== ==== 4.4 - Composition ====
  
-  * Écrire ''compose(...fns)'' (exécution de droite à gauche) et ''pipe(...fns)'' (gauche à droite). +  * Écrire %%compose(...fns)%% (exécution de droite à gauche) et %%pipe(...fns)%% (gauche à droite). 
-  * Construire ''slugify'' en composant : ''trim'', ''toLowerCase'', remplacement des espaces par des tirets, suppression des caractères non alphanumériques.+  * Construire %%slugify%% en composant : %%trim%%, %%toLowerCase%%, remplacement des espaces par des tirets, suppression des caractères non alphanumériques.
  
 <sxh javascript> <sxh javascript>
Ligne 273: Ligne 303:
 </sxh> </sxh>
  
-  * Vérifier que ''compose(f, compose(g, h))'' et ''compose(compose(f, g), h)'' donnent le même résultat (associativité) sur un exemple.+  * Vérifier que %%compose(f, compose(g, h))%% et %%compose(compose(f, g), h)%% donnent le même résultat (associativité) sur un exemple.
  
 ==== 4.5 - Pièges des méthodes natives ==== ==== 4.5 - Pièges des méthodes natives ====
  
-  * Prédire puis exécuter : ''["1", "2", "3"].map(parseInt)''. Expliquer le résultat (arguments passés par ''map'' au callback). Corriger. +  * Prédire puis exécuter : 
-  * Pourquoi ''forEach'' est-il //inadapté// à un style fonctionnel (que retourne-t-il ? quel est son seul intérêt ?) ? + 
-  * ''reduce'' sans valeur initiale sur un tableau vide : que se passe-t-il ? Quelle est la règle pour choisir une valeur initiale (élément neutre) ?+<sxh javascript> 
 +["1", "2", "3"].map(parseInt) 
 +</sxh> 
 + 
 +  * Expliquer le résultat (arguments passés par %%map%% au callback). Corriger. 
 +  * Pourquoi %%forEach%% est-il //inadapté// à un style fonctionnel (que retourne-t-il ? quel est son seul intérêt ?) ? 
 +  * %%reduce%% sans valeur initiale sur un tableau vide : que se passe-t-il ? Quelle est la règle pour choisir une valeur initiale (élément neutre) ?
  
 ==== 4.6 - Pour aller plus loin ==== ==== 4.6 - Pour aller plus loin ====
  
-  * Écrire ''once(fn)'' (n'exécute ''fn'' qu'une fois) et ''debounce'' ou ''retry(n, fn)'' : ces utilitaires sont des HOF "décorateurs" qui ajoutent un comportement sans modifier la fonction d'origine. +  * Écrire %%once(fn)%% (n'exécute %%fn%% qu'une fois) et %%debounce%% ou %%retry(n, fn)%% : ces utilitaires sont des HOF "décorateurs" qui ajoutent un comportement sans modifier la fonction d'origine. 
-  * Écrire ''groupBy(arr, keyFn)'' avec ''reduce'', retournant un objet de tableaux, **sans muter l'accumulateur** (spread à chaque itération). Comparez avec la version qui mute l'accumulateur : quel est le coût, et pourquoi la mutation locale reste-t-elle défendable ici ?+  * Écrire %%groupBy(arr, keyFn)%% avec %%reduce%%, retournant un objet de tableaux, **sans muter l'accumulateur** (spread à chaque itération). Comparez avec la version qui mute l'accumulateur : quel est le coût, et pourquoi la mutation locale reste-t-elle défendable ici ?
  
 <WRAP todo round> <WRAP todo round>
 === Points de discussion === === Points de discussion ===
  
-  * Dans 4.1, qu'est-ce qui rend ''reduce'' plus "puissant" que ''map'' et ''filter'' ? Peut-on exprimer ''reduce'' avec ''map'' ?+  * Dans 4.1, qu'est-ce qui rend %%reduce%% plus "puissant" que %%map%% et %%filter%% ? Peut-on exprimer %%reduce%% avec %%map%% ?
   * Dans 4.2, où vit l'état de la closure ? Comparer avec un objet. Quand une closure est-elle un bon outil d'encapsulation ?   * Dans 4.2, où vit l'état de la closure ? Comparer avec un objet. Quand une closure est-elle un bon outil d'encapsulation ?
   * Le currying favorise-t-il la réutilisation ? Dans quel cas rend-il le code moins lisible (ordre des paramètres) ? Quelle règle pour ordonner les arguments (données en dernier) ?   * Le currying favorise-t-il la réutilisation ? Dans quel cas rend-il le code moins lisible (ordre des paramètres) ? Quelle règle pour ordonner les arguments (données en dernier) ?
-  * ''pipe'' vs enchaînement de méthodes (''arr.map(...).filter(...)'') : avantages et limites de chaque approche ?+  * %%pipe%% vs enchaînement de méthodes (%%arr.map(...).filter(...)%%) : avantages et limites de chaque approche ?
 </WRAP> </WRAP>
  
Ligne 325: Ligne 361:
  
   * Lister **tous** les problèmes (au moins 4 : mutation, effet de bord, mélange calcul/affichage, non-réutilisable, non-testable...).   * Lister **tous** les problèmes (au moins 4 : mutation, effet de bord, mélange calcul/affichage, non-réutilisable, non-testable...).
-  * Exécuter deux fois d'affilée. Que constatez-vous sur ''products'' ?+  * Exécuter deux fois d'affilée. Que constatez-vous sur %%products%% ?
  
 ==== 5.2 - Briques élémentaires pures ==== ==== 5.2 - Briques élémentaires pures ====
Ligne 331: Ligne 367:
 Écrire et tester chacune isolément : Écrire et tester chacune isolément :
  
-  * ''applyDiscount(rate)(product)'' : retourne un **nouveau** produit avec le prix réduit (currying : le taux d'abord). Arrondir à 2 décimales. +  * %%applyDiscount(rate)(product)%% : retourne un **nouveau** produit avec le prix réduit (currying : le taux d'abord). Arrondir à 2 décimales. 
-  * ''inStock(product)'' : prédicat. +  * %%inStock(product)%% : prédicat. 
-  * ''byCategory(cat)(product)'' : prédicat curryfié. +  * %%byCategory(cat)(product)%% : prédicat curryfié. 
-  * ''getPrice(product)'' : sélecteur. +  * %%getPrice(product)%% : sélecteur. 
-  * ''sum(numbers)'' : somme via ''reduce'' (valeur initiale 0).+  * %%sum(numbers)%% : somme via %%reduce%% (valeur initiale 0).
  
 ==== 5.3 - Assemblage ==== ==== 5.3 - Assemblage ====
  
-  * Écrire ''cartTotal(products, rate)'' en composant les briques avec ''pipe'' : filtrer le stock → appliquer la remise → extraire les prix → sommer. +  * Écrire %%cartTotal(products, rate)%% en composant les briques avec %%pipe%% : filtrer le stock, appliquer la remise, extraire les prix, sommer. 
-  * Vérifier : le total attendu est 310.5 pour 10 % de remise sur l'exemple (tests unitaires obligatoires), et ''products'' est inchangé (''Object.freeze'' profond sur les données).+  * Vérifier : le total attendu est **337.5** pour 10 % de remise sur l'exemple (tests unitaires obligatoires), et %%products%% est inchangé (%%Object.freeze%% profond sur les données).
  
 ==== 5.4 - Extensions métier ==== ==== 5.4 - Extensions métier ====
Ligne 346: Ligne 382:
 Chaque extension doit **réutiliser** les briques existantes, pas dupliquer du code : Chaque extension doit **réutiliser** les briques existantes, pas dupliquer du code :
  
-  * ''totalByCategory(products, rate)'' : retourne ''{ tech: ..., book: ..., office: ... }'' (via ''reduce''). +  * %%totalByCategory(products, rate)%% : retourne un objet de la forme suivante (via %%reduce%%) : 
-  * ''applyBulkDiscount'' : une remise de 5 % supplémentaire si le total dépasse 200. Cette règle doit être une fonction pure séparée, composable. + 
-  * ''receipt(products, rate)'' : retourne un tableau de **lignes de texte** (chaînes) décrivant le ticket. **L'affichage (''console.log'') se fait en dehors**, dans une seule fonction impure en bout de chaîne.+<sxh javascript> 
 +{ tech: ..., book: ..., office: ... } 
 +</sxh> 
 + 
 +  * %%applyBulkDiscount%% : une remise de 5 % supplémentaire si le total dépasse 200. Cette règle doit être une fonction pure séparée, composable. 
 +  * %%receipt(products, rate)%% : retourne un tableau de **lignes de texte** (chaînes) décrivant le ticket. **L'affichage (%%console.log%%) se fait en dehors**, dans une seule fonction impure en bout de chaîne.
  
 ==== 5.5 - Analyse de la pureté (partie rédigée) ==== ==== 5.5 - Analyse de la pureté (partie rédigée) ====
Ligne 355: Ligne 396:
  
 ^ Fonction ^ Pure ? ^ Mutation ? ^ Transparente ? ^ Remplaçable par sa valeur ? ^ ^ Fonction ^ Pure ? ^ Mutation ? ^ Transparente ? ^ Remplaçable par sa valeur ? ^
-| ''applyDiscount'' | | | | | +| %%applyDiscount%% | | | | | 
-| ''cartTotal'' | | | | | +| %%cartTotal%% | | | | | 
-| ''receipt'' | | | | |+| %%receipt%% | | | | |
 | fonction d'affichage | | | | | | fonction d'affichage | | | | |
  
-Ensuite, **démontrer** la transparence référentielle de ''cartTotal'' : choisissez un appel, remplacez-le par sa valeur dans un autre calcul, et vérifiez l'équivalence.+Ensuite, **démontrer** la transparence référentielle de %%cartTotal%% : choisissez un appel, remplacez-le par sa valeur dans un autre calcul, et vérifiez l'équivalence.
  
 ==== 5.6 - Pour aller plus loin ==== ==== 5.6 - Pour aller plus loin ====
  
-  * Rendre le taux de remise //dépendant du client// (''regular'' 0 %, ''premium'' 10 %, ''vip'' 20 %) sans ''if''/''switch'' en cascade : utilisez un objet de correspondance et la composition. +  * Rendre le taux de remise //dépendant du client// (%%regular%% 0 %, %%premium%% 10 %, %%vip%% 20 %) sans %%if%%/%%switch%% en cascade : utilisez un objet de correspondance et la composition. 
-  * Écrire ''updateStock(products, id, delta)'' qui retourne un nouveau tableau avec le stock mis à jour pour un produit (rappel exercice 2 : mise à jour immuable dans un tableau d'objets). +  * Écrire %%updateStock(products, id, delta)%% qui retourne un nouveau tableau avec le stock mis à jour pour un produit (rappel exercice 2 : mise à jour immuable dans un tableau d'objets). 
-  * Implémenter un //panier// comme un **réducteur** : ''cartReducer(state, action)'' où ''action'' vaut ''{ type: "ADD", product }'' ou ''{ type: "REMOVE", id }''. Rejouez une liste d'actions avec ''reduce''. Observez le lien avec Redux et le pattern "état = fonction des événements passés".+  * Implémenter un //panier// comme un **réducteur** : %%cartReducer(state, action)%% où %%action%% vaut l'un des deux objets suivants : 
 + 
 +<sxh javascript> 
 +{ type: "ADD", product } 
 +{ type: "REMOVE", id } 
 +</sxh> 
 + 
 +  * Rejouez une liste d'actions avec %%reduce%%. Observez le lien avec Redux et le pattern "état = fonction des événements passés". 
  
 <WRAP todo round> <WRAP todo round>
Ligne 374: Ligne 423:
   * Où sont passés les effets de bord ? Pourquoi les repousser à la //périphérie// du programme ("functional core, imperative shell") ?   * Où sont passés les effets de bord ? Pourquoi les repousser à la //périphérie// du programme ("functional core, imperative shell") ?
   * Si un collègue ajoute une règle de remise (ex : Black Friday), quelles fonctions faut-il modifier dans la version impérative ? Dans la version composée ?   * Si un collègue ajoute une règle de remise (ex : Black Friday), quelles fonctions faut-il modifier dans la version impérative ? Dans la version composée ?
-  * La version fonctionnelle parcourt plusieurs fois le tableau (''filter'', ''map'', ''reduce''). Est-ce un problème ? Comment un //transducer// ou un évaluateur paresseux y répond-il ? À partir de quelle taille de données cela compte-t-il vraiment ?+  * La version fonctionnelle parcourt plusieurs fois le tableau (%%filter%%, %%map%%, %%reduce%%). Est-ce un problème ? Comment un //transducer// ou un évaluateur paresseux y répond-il ? À partir de quelle taille de données cela compte-t-il vraiment ?
   * Dans 5.6 (réducteur), pourquoi l'immutabilité rend-elle possible le "time travel debugging" et l'annulation (undo) ?   * Dans 5.6 (réducteur), pourquoi l'immutabilité rend-elle possible le "time travel debugging" et l'annulation (undo) ?
 </WRAP> </WRAP>
- 
-===== Barème indicatif ===== 
- 
-^ Critère ^ Points ^ 
-| Exercices 1 à 3 : correction du code et des tests | 6 | 
-| Exercice 4 : implémentations et compréhension des closures/composition | 5 | 
-| Exercice 5 : qualité de la décomposition, pureté, tests | 6 | 
-| Réponses aux questions de réflexion (justifiées, appuyées sur le code) | 3 | 
  • cnam/utc503/td3.1792454987.txt.gz
  • Dernière modification : il y a 7 jours
  • de jcheron