Différences
Ci-dessous, les différences entre deux révisions de la page.
| Les deux révisions précédentes Révision précédente Prochaine révision | Révision précédente | ||
| cnam:utc503:td3 [2025/08/12 02:35] – modification externe 127.0.0.1 | cnam:utc503:td3 [2026/10/20 09:17] (Version actuelle) – jcheron | ||
|---|---|---|---|
| Ligne 1: | Ligne 1: | ||
| - | ====== TD 3 ====== | + | ====== TD 3 : Pf en JS ====== |
| - | ===== Exercice 1 : Fonctions Pures ===== | + | |
| - | ==== 1 - Création d'une fonction pure simple ==== | + | <WRAP info round> |
| + | **Objectifs du TD** | ||
| - | | + | À la fin de ce TD, vous saurez : |
| + | | ||
| + | * identifier les effets | ||
| + | * écrire des fonctions d' | ||
| + | * composer des fonctions pour construire des traitements de données complexes ; | ||
| + | * refactoriser du code impératif en code fonctionnel. | ||
| - | ==== 2 - Test de la pureté ==== | + | **Prérequis** : syntaxe JS (fonctions fléchées, destructuration, |
| + | **Consigne générale** : pour chaque exercice, écrivez des tests avec '' | ||
| + | </ | ||
| + | |||
| + | <WRAP tip round> | ||
| + | **Mini-référence : mutateurs vs non-mutateurs sur les tableaux** | ||
| + | |||
| + | ^ Mutent le tableau ^ Ne mutent pas ^ | ||
| + | | '' | ||
| + | |||
| + | '' | ||
| + | </ | ||
| + | |||
| + | ===== Exercice 1 : Fonctions pures ===== | ||
| + | |||
| + | **Définition à tester** : une fonction est pure si (1) pour les mêmes arguments elle retourne toujours le même résultat, et (2) elle ne produit aucun effet observable en dehors | ||
| + | |||
| + | ==== 1.1 - Écrire une fonction pure ==== | ||
| + | |||
| + | * Écrire '' | ||
| + | * Écrire les tests suivants : | ||
| + | |||
| + | <sxh javascript> | ||
| + | const input = [1, 2, 3]; | ||
| + | const output = double(input); | ||
| + | console.assert(JSON.stringify(output) === " | ||
| + | console.assert(JSON.stringify(input) === " | ||
| + | console.assert(output !== input); | ||
| + | console.assert(JSON.stringify(double([])) === " | ||
| + | </ | ||
| + | |||
| + | ==== 1.2 - Vérifier | ||
| + | |||
| + | * Appeler '' | ||
| + | * Sans modifier le code, ajoutez un test qui prouve que '' | ||
| + | * Compléter ce tableau pour '' | ||
| + | |||
| + | ^ Critère ^ Respecté ? ^ Justification ^ | ||
| + | | Même entrée → même sortie | | | | ||
| + | | Ne modifie pas ses arguments | | | | ||
| + | | Ne lit aucun état externe | | | | ||
| + | | Ne produit aucun effet observable (console, réseau, DOM, fichier...) | | | | ||
| + | |||
| + | ==== 1.3 - Quatre fonctions impures, quatre sources d' | ||
| + | |||
| + | Pour chacune des fonctions ci-dessous, **identifiez la cause exacte d' | ||
| + | |||
| + | <sxh javascript> | ||
| + | // A | ||
| + | let total = 0; | ||
| + | function addToTotal(n) { total += n; return total; } | ||
| + | |||
| + | // B | ||
| + | function addItem(cart, | ||
| + | |||
| + | // C | ||
| + | function greet(name) { | ||
| + | console.log(" | ||
| + | return name.length; | ||
| + | } | ||
| + | |||
| + | // D | ||
| + | function isExpired(deadline) { return new Date() > deadline; } | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== 1.4 - Pour aller plus loin : l' | ||
| - | * Tester la fonction avec différents ensembles de données. | + | * Cette fonction est-elle pure ? Justifiez en écrivant un test qui le démontre ou l' |
| - | * Identifier les caractéristiques qui font que la fonction est pure. | + | |
| - | ==== 3 - Exemple d'une fonction impure ==== | + | <sxh javascript> |
| + | function normalize(users) { | ||
| + | return users.map(u | ||
| + | } | ||
| + | </ | ||
| - | * Créer une fonction qui modifie une variable extérieure (effet de bord) et expliquer pourquoi elle est impure. | + | * Corrigez-la. Que constatez-vous sur la différence entre //" |
| <WRAP todo round> | <WRAP todo round> | ||
| === Points de discussion === | === Points de discussion === | ||
| - | * Pourquoi | + | * Dans 1.3, laquelle des quatre sources d' |
| - | * Quels types de bugs ou de problèmes peuvent survenir | + | * Une fonction qui fait '' |
| + | * Dans 1.4, '' | ||
| + | * Pourquoi une fonction pure est-elle triviale à tester, contrairement à A, B, C et D ? | ||
| </ | </ | ||
| + | |||
| ===== Exercice 2 : Immutabilité ===== | ===== Exercice 2 : Immutabilité ===== | ||
| - | ==== 1 - Ajout d'un élément | + | **Objectif** : produire des " |
| - | * Écrire une fonction qui ajoute un élément à un tableau sans modifier le tableau d' | + | ==== 2.1 - Tableaux : les quatre opérations de base ==== |
| - | ==== 2 - Comparaison avec une approche mutable ==== | + | Écrire quatre fonctions pures, sans mutateur, qui retournent un nouveau tableau : |
| - | * Modifier directement un tableau en utilisant `.push()` et observer les changements sur le tableau d'origine. | + | * '' |
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| - | ==== 3 - Modification immutable d'un objet ==== | + | Vérifier avec '' |
| - | | + | ==== 2.2 - Observer la mutation ==== |
| + | |||
| + | | ||
| + | |||
| + | <sxh javascript> | ||
| + | const a = [3, 1, 2]; | ||
| + | const b = a; | ||
| + | b.push(4); | ||
| + | const c = a.sort(); | ||
| + | console.log(a, | ||
| + | console.log(a === b, a === c); | ||
| + | </ | ||
| + | |||
| + | * Expliquer en 3 phrases ce qu' | ||
| + | |||
| + | ==== 2.3 - Objets : mises à jour immuables ==== | ||
| + | |||
| + | Soit : | ||
| + | |||
| + | <sxh javascript> | ||
| + | const user = { | ||
| + | name: " | ||
| + | address: { city: " | ||
| + | tags: [" | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | Écrire **sans muter** '' | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | Pour chaque fonction, tester que l' | ||
| + | |||
| + | ==== 2.4 - Le piège de la copie superficielle ==== | ||
| + | |||
| + | * Que fait ce code ? Démontrez le bug par un test. | ||
| + | |||
| + | <sxh javascript> | ||
| + | const copy = { ...user }; | ||
| + | copy.address.city = " | ||
| + | console.log(user.address.city); | ||
| + | </ | ||
| + | |||
| + | * Corrigez de deux façons : (a) par copie imbriquée avec spread, (b) avec '' | ||
| + | |||
| + | ==== 2.5 - Pour aller plus loin : mise à jour profonde générique ==== | ||
| + | |||
| + | * Écrire '' | ||
| + | |||
| + | <sxh javascript> | ||
| + | const updated = setIn(user, [" | ||
| + | console.assert(updated.address.city === " | ||
| + | console.assert(user.address.city === " | ||
| + | console.assert(updated.tags === user.tags); | ||
| + | </ | ||
| <WRAP todo round> | <WRAP todo round> | ||
| === Points de discussion === | === Points de discussion === | ||
| - | * Pourquoi l'immutabilité | + | * Dans 2.2, quel bug concret une référence partagée peut-elle provoquer dans une application où deux composants utilisent le même tableau ? |
| - | * Quels avantages cela présente-t-il pour la gestion des états | + | * '' |
| + | * Copier toute la structure à chaque modification est-il coûteux ? Comment le //partage structurel// | ||
| + | * Citez un cas où la mutation locale est acceptable (ex : accumulateur | ||
| </ | </ | ||
| - | ===== Exercice 3 : Transparence Référentielle ===== | ||
| - | ==== 1 - Transparence | + | ===== Exercice 3 : Transparence |
| - | | + | **Définition** : une expression est référentiellement transparente si on peut la remplacer |
| - | * Remplacer les appels de fonction dans le code par les valeurs retournées et tester | + | |
| - | ==== 2 - Transparence dans des expressions complexes | + | ==== 3.1 - Substitution sur des fonctions simples |
| - | * Introduire des fonctions imbriquées | + | * Écrire %%add(a, b)%% et %%mul(a, b)%%. |
| - | * Tester si le programme reste identique. | + | * Soit l' |
| - | ==== 3 - Absence de transparence référentielle ==== | + | <sxh javascript> |
| + | add(mul(2, | ||
| + | </ | ||
| - | * Créer une fonction avec des effets | + | * 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> | ||
| + | const x = mul(2, 3); | ||
| + | add(x, x) | ||
| + | </ | ||
| + | |||
| + | * Pourquoi cette factorisation est-elle sûre ? | ||
| + | |||
| + | ==== 3.2 - Détecter les ruptures de transparence ==== | ||
| + | |||
| + | Pour chacune | ||
| + | |||
| + | <sxh javascript> | ||
| + | // A | ||
| + | let counter = 0; | ||
| + | const next = () => ++counter; | ||
| + | |||
| + | // B | ||
| + | const rand = () => Math.random(); | ||
| + | |||
| + | // C | ||
| + | const now = () => Date.now(); | ||
| + | |||
| + | // D (TAX est modifiable ailleurs dans le programme) | ||
| + | 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; | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | Pour chaque cas, reformulez : //quelle information supplémentaire (hors arguments) la fonction utilise-t-elle | ||
| + | |||
| + | ==== 3.3 - Rendre transparent ==== | ||
| + | |||
| + | * Réécrivez **A** pour qu'il soit référentiellement transparent (indice : l' | ||
| + | * 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 | ||
| + | </ | ||
| + | |||
| + | * Montrez que %%random(42)%% donne toujours le même résultat. | ||
| + | |||
| + | ==== 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)%%). | ||
| + | * 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 ? | ||
| <WRAP todo round> | <WRAP todo round> | ||
| === Points de discussion === | === Points de discussion === | ||
| - | * Pourquoi la transparence référentielle | + | * Pourquoi la mémoïsation n'est-elle |
| - | * Comment la transparence référentielle | + | * 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 // |
| + | * Une fonction peut être pure sans que son //appel// soit " | ||
| + | * 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 | ||
| </ | </ | ||
| + | |||
| ===== Exercice 4 : Fonctions d' | ===== Exercice 4 : Fonctions d' | ||
| - | ==== 1 - Application d'une fonction | + | **Définition** : une fonction d'ordre supérieur prend une fonction en argument et/ou retourne une fonction. |
| - | * Écrire une fonction d’ordre supérieur qui prend une fonction et l’applique à chaque élément d’un tableau. | + | ==== 4.1 - Reconstruire les classiques ==== |
| - | ==== 2 - Création | + | Sans utiliser les méthodes natives correspondantes, |
| - | * Écrire une fonction qui retourne une autre fonction | + | * %%myMap(arr, fn)%% ; |
| + | * %%myFilter(arr, | ||
| + | * %%myReduce(arr, | ||
| - | ==== 3 - Utilisation | + | Puis **réimplémenter %%myMap%% et %%myFilter%% uniquement à partir |
| - | | + | ==== 4.2 - Fonctions qui retournent des fonctions (closures) ==== |
| + | |||
| + | | ||
| + | * %%greaterThan(n)%% retourne un prédicat. L' | ||
| + | * %%counter()%% retourne une fonction qui retourne 1, 2, 3... à chaque appel. **Cette fonction est-elle pure ?** Où est stocké l' | ||
| + | |||
| + | ==== 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 %%partial(fn, | ||
| + | * Utiliser sur les fonctions suivantes : | ||
| + | |||
| + | <sxh javascript> | ||
| + | const add3 = (a, b, c) => a + b + c; | ||
| + | const formatPrice = (currency, decimals, value) => /* à compléter */; | ||
| + | </ | ||
| + | |||
| + | * Créer %%formatEUR%% | ||
| + | |||
| + | ==== 4.4 - Composition ==== | ||
| + | |||
| + | * Écrire %%compose(...fns)%% (exécution de droite à gauche) et %%pipe(...fns)%% (gauche à droite). | ||
| + | * Construire %%slugify%% en composant : %%trim%%, %%toLowerCase%%, | ||
| + | |||
| + | <sxh javascript> | ||
| + | console.assert(slugify(" | ||
| + | </ | ||
| + | |||
| + | * Vérifier que %%compose(f, | ||
| + | |||
| + | ==== 4.5 - Pièges des méthodes natives ==== | ||
| + | |||
| + | * Prédire puis exécuter : | ||
| + | |||
| + | <sxh javascript> | ||
| + | [" | ||
| + | </ | ||
| + | |||
| + | * Expliquer le résultat (arguments passés par %%map%% au callback). Corriger. | ||
| + | * Pourquoi %%forEach%% est-il // | ||
| + | * %%reduce%% sans valeur initiale sur un tableau vide : que se passe-t-il ? Quelle est la règle | ||
| + | |||
| + | ==== 4.6 - Pour aller plus loin ==== | ||
| + | |||
| + | * Écrire %%once(fn)%% (n' | ||
| + | * Écrire %%groupBy(arr, | ||
| <WRAP todo round> | <WRAP todo round> | ||
| === Points de discussion === | === Points de discussion === | ||
| - | * En quoi les fonctions d’ordre supérieur facilitent-elles la modularité du code ? | + | * Dans 4.1, qu'est-ce qui rend %%reduce%% plus " |
| - | * Comment ce concept peut-il être utilisé pour encapsuler | + | * Dans 4.2, où vit l' |
| + | * Le currying favorise-t-il la réutilisation ? Dans quel cas rend-il le code moins lisible (ordre | ||
| + | * %%pipe%% vs enchaînement de méthodes (%%arr.map(...).filter(...)%%) : avantages et limites de chaque approche | ||
| </ | </ | ||
| - | ===== Exercice 5 : Combinaison des principes ===== | ||
| - | ==== 1 - Manipulation | + | ===== Exercice 5 : Synthèse : panier d' |
| + | |||
| + | **Contexte** : vous développez le calcul d'un panier pour un site e-commerce. Toutes les fonctions doivent être **pures** et **sans mutation**. | ||
| + | |||
| + | <sxh javascript> | ||
| + | const products = [ | ||
| + | { id: 1, name: " | ||
| + | { id: 2, name: " | ||
| + | { id: 3, name: "Livre JS", price: 40, category: " | ||
| + | { id: 4, name: " | ||
| + | { id: 5, name: " | ||
| + | ]; | ||
| + | </ | ||
| + | |||
| + | ==== 5.1 - Version impérative (point | ||
| + | |||
| + | On vous donne ce code : | ||
| + | |||
| + | <sxh javascript> | ||
| + | let total = 0; | ||
| + | for (let i = 0; i < products.length; | ||
| + | if (products[i].stock > 0) { | ||
| + | products[i].price = products[i].price * 0.9; | ||
| + | total += products[i].price; | ||
| + | } | ||
| + | } | ||
| + | console.log(" | ||
| + | </ | ||
| + | |||
| + | * Lister **tous** les problèmes (au moins 4 : mutation, effet de bord, mélange calcul/ | ||
| + | * Exécuter deux fois d'affilée. Que constatez-vous sur %%products%% ? | ||
| + | |||
| + | ==== 5.2 - Briques élémentaires pures ==== | ||
| + | |||
| + | Écrire et tester chacune isolément : | ||
| + | |||
| + | * %%applyDiscount(rate)(product)%% : retourne un **nouveau** produit avec le prix réduit (currying : le taux d' | ||
| + | * %%inStock(product)%% : prédicat. | ||
| + | * %%byCategory(cat)(product)%% : prédicat curryfié. | ||
| + | * %%getPrice(product)%% : sélecteur. | ||
| + | * %%sum(numbers)%% : somme via %%reduce%% (valeur initiale 0). | ||
| + | |||
| + | ==== 5.3 - Assemblage ==== | ||
| + | |||
| + | * Écrire %%cartTotal(products, | ||
| + | * Vérifier : le total attendu est **337.5** pour 10 % de remise sur l' | ||
| + | |||
| + | ==== 5.4 - Extensions métier ==== | ||
| + | |||
| + | Chaque extension doit **réutiliser** les briques existantes, pas dupliquer du code : | ||
| + | |||
| + | * %%totalByCategory(products, | ||
| + | |||
| + | <sxh javascript> | ||
| + | { tech: ..., book: ..., office: ... } | ||
| + | </ | ||
| + | |||
| + | * %%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, | ||
| + | |||
| + | ==== 5.5 - Analyse de la pureté (partie rédigée) ==== | ||
| + | |||
| + | Pour chaque fonction écrite, remplir : | ||
| + | |||
| + | ^ Fonction ^ Pure ? ^ Mutation ? ^ Transparente ? ^ Remplaçable par sa valeur ? ^ | ||
| + | | %%applyDiscount%% | | | | | | ||
| + | | %%cartTotal%% | | | | | | ||
| + | | %%receipt%% | | | | | | ||
| + | | fonction d' | ||
| + | |||
| + | 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' | ||
| - | * Travailler sur un tableau d' | + | ==== 5.6 - Pour aller plus loin ==== |
| - | * Appliquer une réduction à chaque produit tout en conservant l' | + | |
| - | ==== 2 - Calcul total avec `reduce` ==== | + | * Rendre le taux de remise // |
| + | * Écrire %%updateStock(products, | ||
| + | * Implémenter un //panier// comme un **réducteur** : %%cartReducer(state, | ||
| - | * Calculer le prix total après réduction en utilisant `reduce`. | + | <sxh javascript> |
| + | { type: " | ||
| + | { type: " | ||
| + | </ | ||
| - | ==== 3 - Refactorisation pour pureté | + | * Rejouez une liste d' |
| - | * Réécrire le code en appliquant les principes vus, comme découper en plus petites fonctions pures. | ||
| <WRAP todo round> | <WRAP todo round> | ||
| === Points de discussion === | === Points de discussion === | ||
| - | * Quel est l'impact | + | * Entre 5.1 et 5.3, qu'avez-vous gagné en testabilité ? Combien |
| - | * Pouvez-vous voir où la transparence référentielle intervient | + | * Où sont passés les effets de bord ? Pourquoi les repousser à la // |
| - | * Comment | + | * Si un collègue ajoute une règle de remise (ex : Black Friday), quelles fonctions faut-il modifier |
| - | * Quelles améliorations peuvent être apportées pour rendre | + | * La version fonctionnelle parcourt plusieurs fois le tableau (%%filter%%, |
| + | * Dans 5.6 (réducteur), | ||
| </ | </ | ||