Techniques de Programmation Fonctionnelle
Les piliers
- Les fonctions pures sont simples et puissantes
- Les fonctions sont des valeurs commes les autres
- Les états partagés, les variables muables, et les effets de bord sont à bannir
- Exprimer le "quoi", sans se soucier du "comment"
Les fonctions pures
- Ont toujours la même sortie pour les même entrées
- N'ont pas d'effets de bords
Un effet de bord est n'importe quel changement observable à l'extérieur de la fonction, autre que sa valeur de retour.
Des exemples ?
- Modifier une variable ou un objet externe
- Logger, afficher quelque chose
- Écrire dans un fichier
- Communiquer sur le réseau
- Appeler une fonction qui a des effets de bord
let PI = 3.14;
function area(radius) {
return radius * radius * PI
}
Impures
function area(radius, pi) {
return radius * radius * pi;
}
Pures
function append(array, x) {
array.push(x)
return array;
}
function append(array, x) {
return array.concat([x]);
}
Une fonction, si elle est pure, est :
- Prédictible et isolée
- Simple à comprendre, à utiliser
- Simple à intégrer, à retirer
- Facile à tester, à debugger
Fonctions, des valeurs comme les autres
Une fonction est une valeur, elle peut être assignée, passée en paramètre ou renvoyée en résultat
/* A function is a value and can be assigned */
const double = n => n * 2;
/* Function as parameter */
function mapper(fn) {
/* Function as returned value */
return array => array.map(fn);
}
const doubleArray = mapper(double);
doubleArray([1, 2, 3]); // [2, 4, 6]
État partagés et mutabilité
const x = {
val: 2
};
const incrementX = () => (x.val += 1);
const doubleX = () => (x.val *= 2);
incrementX();
doubleX();
console.log(x.val); // 6
// Same, but...
const y = {
val: 2
};
const incrementY = () => (y.val += 1);
const doubleY = () => (y.val *= 2);
// ...the order is reversed...
doubleY();
incrementY();
// ... which changes the result
console.log(y.val); // 5
État partagés et mutabilité
doivent être évités
const x = {
val: 2
};
const increment = x => ({ ...x, val: x.val + 1 });
const double = x => ({ ...x, val: x.val * 2 });
console.log(double(increment(x)).val); // 6
// No dependency on outside variables,
// no need for a different function for y
const y = {
val: 2
};
// You can call anything, in any order...
increment(y);
double(y);
increment(double(x));
// ... it will not change the result of other calls
console.log(double(increment(x)).val); // 6
Déclaratif vs Impératif
Le quoi et non le comment
// Quel est l'age moyen des femmes dans ce groupe ?
const casa = [
{ name: "Le Professeur", gender: "male", age: 43 },
{ name: "Tokyo", gender: "female", age: 28 },
{ name: "Nairobi", gender: "female", age: 32 }
];
function femaleAgeAverage(group) {
let sum = 0;
let femaleCount = 0;
for (let i = 0; i < group.length; i++) {
const member = group[i];
if (member.gender === "female") {
sum += member.age;
femaleCount += 1;
}
}
return sum / femaleCount;
}
Déclaratif vs Impératif
Le quoi et non le comment
// Quel est l'age moyen des femmes dans ce groupe ?
const casa = [
{ name: "Le Professeur", gender: "male", age: 43 },
{ name: "Tokyo", gender: "female", age: 28 },
{ name: "Nairobi", gender: "female", age: 32 }
];
const isFemale = p => p.gender === "female";
const getAge = p => p.age;
const sum = a => a.reduce((s, n) => s + n, 0);
const average = a => sum(a) / a.length;
function femaleAgeAverage(group) {
const females = group.filter(isFemale);
const femaleAges = females.map(getAge);
return average(femaleAges);
}
En pratique
- Décomposer son code en fonctions simples
- Favoriser les fonctions pures
- Limiter les endroits où il y a des side-effects
- Éviter les `for`, `while`, `let`
- Retourner des copies plutôt que de modifier
Récursivité
"Quand une fonction se rappelle elle même"
- Si on sait résoudre un certain problème sur des cas évidents (ex: liste vide, ou de taille 1)
- Si, ayant résolu le problème sur un cas donné, on sait le résoudre sur un cas similaire mais un peu plus grand
Alors c'est gagné, on peut faire de la récursion !
Récursivité
En pratique
- Vérifier que les cas de bases (cas d'arrêt) sont gérés.
Sinon boucle infinie... - Vérifier que les appels récursifs sont toujours fait sur des paramètres plus petits, de manière à atteindre les cas d'arrêt. Sinon boucle infinie...
À la fin, c'est souvent élégant
Techniques de Programmation Fonctionnelle
By Nicolas Gaborit
Techniques de Programmation Fonctionnelle
Les principes de base, leurs intérêt, et ce qui est bien de faire en pratique
- 69