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 [2024/10/17 00:20] – jcheron | 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 immuable d'un objet ==== | + | Vérifier avec '' |
| - | * Écrire une fonction qui modifie une propriété d'un objet sans altérer l' | + | ==== 2.2 - Observer la mutation ==== |
| + | * Exécuter ce code et **prédire le résultat avant** de lancer : | ||
| + | |||
| + | <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'est une // | ||
| + | |||
| + | ==== 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> | ||
| === 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 | + | ===== Exercice 3 : Transparence |
| - | ==== 1 - Transparence dans une fonction simple ==== | + | **Définition** : une expression est référentiellement transparente si on peut la remplacer par sa valeur sans changer le comportement du programme. |
| - | * Créer une fonction qui calcule la somme de deux nombres. | + | ==== 3.1 - Substitution sur des fonctions simples ==== |
| - | * Remplacer les appels de fonction dans le code par les valeurs retournées et tester le comportement. | + | |
| - | ==== 2 - Transparence dans des expressions complexes ==== | + | * Écrire %%add(a, b)%% et %%mul(a, b)%%. |
| + | * Soit l' | ||
| - | * Introduire des fonctions imbriquées et remplacer les appels par leurs valeurs. | + | <sxh javascript> |
| - | * Tester si le programme reste identique. | + | add(mul(2, 3), mul(2, 3)) |
| + | </ | ||
| - | ==== 3 - Absence | + | * Remplacer **à la main**, étape par étape, chaque appel par sa valeur (modèle |
| + | * Factoriser : | ||
| - | * Créer une fonction avec des effets de bord (ex: `Math.random()` ou modification d'une variable globale) et expliquer pourquoi elle n'est pas transparente. | + | <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 des fonctions suivantes, dire si elle est transparente et justifier. Tester en appelant deux fois de suite avec les mêmes arguments. | ||
| + | |||
| + | <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 ou produit-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' | ||
| + | |||
| + | * É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> | ||
| === 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 |
| - | * Expérimenter avec les méthodes comme `filter()`, `reduce()`, et `forEach()` pour transformer des tableaux. | + | ==== 4.2 - Fonctions qui retournent des fonctions |
| - | === Points | + | * %%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' | ||
| + | * %%counter()%% retourne une fonction qui retourne 1, 2, 3... à chaque appel. **Cette fonction est-elle pure ?** Où est stocké l' | ||
| - | * En quoi les fonctions d’ordre supérieur facilitent-elles la modularité du code ? | + | ==== 4.3 - Currying |
| - | * Comment ce concept peut-il être utilisé pour encapsuler des comportements | + | |
| - | ===== Exercice 5 : Combinaison des principes ===== | + | * É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 | ||
| - | ==== 1 - Manipulation de tableaux d' | + | <sxh javascript> |
| + | const add3 = (a, b, c) => a + b + c; | ||
| + | const formatPrice | ||
| + | </ | ||
| - | * Travailler sur un tableau d' | + | * Créer %%formatEUR%% et %%formatUSD2%% |
| - | * Appliquer une réduction | + | |
| - | ==== 2 - Calcul total avec `reduce` | + | ==== 4.4 - Composition |
| - | * Calculer le prix total après réduction | + | * Écrire %%compose(...fns)%% (exécution de droite à gauche) et %%pipe(...fns)%% (gauche à droite). |
| + | * Construire %%slugify%% | ||
| - | ==== 3 - Refactorisation pour pureté et réutilisabilité ==== | + | <sxh javascript> |
| + | console.assert(slugify(" | ||
| + | </ | ||
| - | * Réécrire le code en appliquant les principes vus, comme découper en plus petites fonctions pures. | + | * Vérifier que %%compose(f, compose(g, h))%% et %%compose(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 pour choisir une valeur initiale (élément neutre) ? | ||
| + | |||
| + | ==== 4.6 - Pour aller plus loin ==== | ||
| + | |||
| + | * Écrire %%once(fn)%% (n' | ||
| + | * Écrire %%groupBy(arr, | ||
| + | |||
| + | <WRAP todo round> | ||
| === Points de discussion === | === Points de discussion === | ||
| - | * Quel est l'impact | + | * Dans 4.1, qu'est-ce qui rend %%reduce%% plus " |
| - | * Pouvez-vous voir où la transparence référentielle intervient dans ce code ? | + | * Dans 4.2, où vit l'état de la closure ? Comparer avec un objet. Quand une closure est-elle un bon outil d'encapsulation |
| - | * Comment la combinaison | + | * 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) |
| - | * Quelles améliorations peuvent être apportées pour rendre le code plus fonctionnel ? | + | * %%pipe%% vs enchaînement |
| + | </ | ||
| + | ===== 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 de départ) ==== | ||
| + | |||
| + | 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' | ||
| + | |||
| + | ==== 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' | ||
| + | |||
| + | ==== 5.6 - Pour aller plus loin ==== | ||
| + | |||
| + | * Rendre le taux de remise // | ||
| + | * Écrire %%updateStock(products, | ||
| + | * Implémenter un //panier// comme un **réducteur** : %%cartReducer(state, | ||
| + | |||
| + | <sxh javascript> | ||
| + | { type: " | ||
| + | { type: " | ||
| + | </ | ||
| + | |||
| + | * Rejouez une liste d' | ||
| + | |||
| + | |||
| + | <WRAP todo round> | ||
| + | === Points de discussion === | ||
| + | |||
| + | * Entre 5.1 et 5.3, qu' | ||
| + | * Où sont passés les effets de bord ? Pourquoi les repousser à la // | ||
| + | * 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%%, | ||
| + | * Dans 5.6 (réducteur), | ||
| + | </ | ||