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

  1. Ont toujours la même sortie pour les même entrées
  2. 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