Ce TP a pour but de vous familiariser à la programmation fonctionnelle en écrivant quelques décorateurs.
Une deuxième partie est consacrée à l’API fonctionnelle de la classe Array
.
A faire :
once
, maybe
et memoize
;src/decorator.mjs
Un décorateur est une fonction qui “modifie le comportement d’une autre fonction”. Plus précisément, c’est une fonction deco qui prend une fonction fct en argument, telle que deco(fct) est la fonction fct dont le comportement est modifié.
Fichiers de départ tp1.html et tp1.js.
chrono
On donner l’exemple suivant, d’un décorateur qui affiche le temps d’exécution
function chrono(fct) {
// c.f. https://javascript.info/rest-parameters-spread
return function (...args) {
const start = Date.now();
const res = fct(...args);
const end = Date.now();
console.info(`${fct.name}(...) executed in ${end - start}ms`);
return res;
};
}
function f(x, y, z) {
return 2 * x - y + 3 * z;
}
function g(arr) {
return arr.reduce((a, b) => a + b, 0);
}
console.log(f(1, 2, 3));
const _f = chrono(f);
console.log(_f(1, 2, 3));
setTimeout(() => _f(1, 3, 3), 1000);
chrono
sur la fonction g
de sorte à ce que le temps passé soit substantiel.f
au bout de 1 seconde dans un setTimeout(() => f(1, 2, 3), 1000)
, quel sera le temps affiché par chrono
? Le prévoir avant de tester.Ecrire les décorateurs suivants. On ne demande pas de gérer les exceptions.
once(fct)
: au premier appel, renvoie le résultat renvoyé par fct
et renvoie ce même résultat, quels que soient les arguments passés en paramètres, pour les appels suivants (sans rappeller fct
).
fct
unaire, puis avec les Rest parameters and spread syntax, passez au cas général.maybe(fct, def)
: appelle fct
et si fct
renvoie undefined
, alors renvoie def
à la place, une valeur par défaut.
fct
unaire, puis avec les Rest parameters and spread syntax, passez au cas général.fct
ne soit bien appellée qu’une seule fois.memoize(fct)
: si fct
(supposée unaire) a déjà été appelée avec le même argument, renvoie directement 1 la valeur retournée précédemment par fct
pour cet argument.
once(fct)
, avec un dictionnaire pour enregistrer plusieurs valeurs au lieu d’une seule. Utiliser Map
chain(n)(fct)
enchaine la fonction fct
(supposée unaire) n fois, c’est-à-dire renvoie la fonction x ↦ fct(fct(…(fct(x))…)) où fct est appellée n fois. Si l’entier n est nul alors la fonction identité est renvoyée x ↦ x. On donnera une version itérative et une version récursive de chain(n)(fct)
.Voir cet exemple pour l’utilisation des rest parameters et de la spread syntax.
Pour aller plus loin ou si vous faites du Python régulièrement.
Le terme de décorateur est issu de la terminologie Python, où ses fonctions bénéficient même d’une syntaxe ad hoc, voir par exemple, le module functools
.
from time import time
from functools import wraps
def chrono(fct):
"""From https://stackoverflow.com/questions/51503672/decorator-for-timeit-timeit-method"""
@wraps(fct)
def inner(*args, **kwargs):
start = int(round(time() * 1_000))
try:
return fct(*args, **kwargs)
finally:
end_ = int(round(time() * 1_000)) - start
print(f"{fct.__name__}() execution time: {end_ if end_ > 0 else 0} ms")
return inner
@chrono
def somme(l):
return sum(l)
print(somme(list(range(int(1e7)))))
Array
La classe Array
dispose des méthodes suivantes qui prennent toutes une fonction en paramètre :
Voir le fichier demo.js pour comparer les approches impératives classiques (avec boucle for explicite et affectation) et fonctionnelles sur un calcul simple.
Pour chacune des variables définies dans le programme suivant, essayer de prédire sa valeur puis vérifier (dans la console JavaScript ou avec Node.js).
const tab = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const t1 = tab.filter((n) => n > 3);
const t2 = tab.map((n) => n + 10);
const v1 = tab.some((n) => n !== 3 && n % 3 === 0);
const v2 = tab.some((n) => n !== 5 && n % 5 === 0);
const t3 = tab.filter((n) => n % 2 === 0).map((n) => 2 * n);
const t4 = tab.map((n) => 2 * n).filter((n) => n % 2 === 0);
// ici on utilise un if expression ou "if ternaire"
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator
const v3 = tab.reduce((acc, n) => acc + (n % 2 === 0 ? n * n : 0), 0);
const v4 = tab
.filter((n) => n % 2 === 0)
.map((n) => n * n)
.reduce((acc, n2) => acc + n2, 0);
const v5 = tab.filter((n) => n <= 4).reduce((acc, n) => acc * n, 1);
const v6 = tab
.filter((n) => n % 3 === 0)
.map((n) => ({ v: n, c: 1 }))
.reduce((acc, o) => ({ s: acc.s + o.v, c: acc.c + o.c }), { s: 0, c: 0 });
const v7 = ((o) => o.s / o.c)(v6);
On considère maintenant un tableau d’objets comme suit :
const arr = [
{ name: "Alice", age: 40, sal: 250000 },
{ name: "Bob", age: 17, sal: -1 },
{ name: "Charlie", age: 30, sal: 180000 },
{ name: "Denise", age: 12, sal: -1 },
];
En utilisant l’API Array
, sans utiliser de boucle for
explicite et avec uniquement des variables const
, écrire les fonctions suivantes qui prennent toutes en paramètre un tableau d’objets comme arr
et :
true
si et seulement tous les majeurs ont un salaire supérieur à 150000nom: age
ne contenant que les personnes majeures.reduce
qui va compter la somme des salaires et en parallèle le nombre de majeurs, l’exercice précédent vous a donné une bonne partie de la réponse.Sur l’exemple, on devrait obtenir :
console.log(exo1(arr));
// true
console.log(exo2(arr));
// [ 'Alice: 40', 'Charlie: 30' ]
console.log(exo3(arr));
// 215000
On considère le décorateur m
suivant :
function m(f) {
function w(...a) {
w.x.push(a);
return f(...a);
}
w.x = [];
return w;
}
const adder_decorated = m((a, b) => a + b);
console.log(adder_decorated(2, 3));
console.log(adder_decorated(3, 4));
m
, f
, w
, a
et x
pour rendre le code clair.On souhaite modifier tp1.html et tp1.js pour créer des boutons qui modifieront la taille d’une citation (élément HTML <q>
).
Il faut que quand on clique sur le bouton Ajouter modificateur de taille et que le champ de saisi vaut $n$, un nouveau bouton soit ajouté à la page, et que quand on clique à son tour dessus, celui-ci mettra la citation à la taille $n$.
Ci-dessous, un exemple ou trois boutons ont été ajoutés, pour passer le texte aux tailles 12, 14 ou 18. Là, on a cliqué sur le bouton Taille 14 et le texte a grossi.
i.e. sans rappeler fct
↩