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:16] – [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 228: Ligne 238:
  
   * 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 ====
Ligne 269: Ligne 285:
   * É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 : 
 + 
 +<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%%.   * Créer %%formatEUR%% et %%formatUSD2%% à partir de %%formatPrice%%.
  
Ligne 276: Ligne 298:
   * É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.
-  * Test à satisfaire : %%console.assert(slugify("  Hello World !  ") === "hello-world");%%+ 
 +<sxh javascript> 
 +console.assert(slugify("  Hello World !  ") === "hello-world"); 
 +</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 : 
 + 
 +<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 ?) ?   * 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) ?   * %%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) ?
Ligne 303: Ligne 335:
 **Contexte** : vous développez le calcul d'un panier pour un site e-commerce. Toutes les fonctions doivent être **pures** et **sans mutation**. **Contexte** : vous développez le calcul d'un panier pour un site e-commerce. Toutes les fonctions doivent être **pures** et **sans mutation**.
  
-Jeu de données de départ : +<sxh javascript> 
- +const products = [ 
-^ id ^ name ^ price ^ category ^ stock ^ +  { id: 1, name: "Clavier",  price: 80,  category: "tech",   stock: 5 }, 
-| 1 | Clavier | 80 | tech | 5 | +  { id: 2, name: "Souris",   price: 30,  category: "tech",   stock: 0 }, 
-| 2 | Souris | 30 | tech | 0 | +  { id: 3, name: "Livre JS", price: 40,  category: "book",   stock: 12 }, 
-| 3 | Livre JS | 40 | book | 12 | +  { id: 4, name: "Cahier",   price: 5,   category: "office", stock: 100 }, 
-| 4 | Cahier | 5 | office | 100 | +  { id: 5, name: "Écran",    price: 250, category: "tech",   stock: 2 } 
-| 5 | Écran | 250 | tech | 2 | +]; 
- +</sxh>
-À transcrire en JS sous la forme d'un tableau d'objets %%{ id, name, price, category, stock }%% nommé %%products%%.+
  
 ==== 5.1 - Version impérative (point de départ) ==== ==== 5.1 - Version impérative (point de départ) ====
  
-On vous donne ce code, à recopier et exécuter :+On vous donne ce code :
  
-  * %%let total = 0;%% +<sxh javascript> 
-  * %%for (let i = 0; i < products.length; i++) {%% +let total = 0; 
-  * %%  if (products[i].stock > 0) {%% +for (let i = 0; i < products.length; i++) { 
-  * %%    products[i].price = products[i].price * 0.9;%% +  if (products[i].stock > 0) { 
-  * %%    total += products[i].price;%% +    products[i].price = products[i].price * 0.9; 
-  * %%  }%% +    total += products[i].price; 
-  * %%}%% +  } 
-  * %%console.log("Total : " + total);%% +} 
- +console.log("Total : " + total); 
-Questions :+</sxh>
  
   * 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...).
Ligne 351: 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%%) : 
 + 
 +<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.   * %%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.   * %%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.
Ligne 371: Ligne 407:
   * 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>
  • cnam/utc503/td3.1792455395.txt.gz
  • Dernière modification : il y a 7 jours
  • de jcheron